Leetcode第21题至第30题 思路分析及C++实现

笔者按照目录刷题,对于每一道题,力争使用效率最高(时间复杂度最低)的算法,并全部通过C++代码实现AC。(文中计算的复杂度都是最坏情况复杂度)
因为考虑到大部分读者已经在Leetcode浏览过题目了,所以每道题都按照 解题思路、实现代码 的顺序进行讲解。
(笔者目前已刷 120 题,已更新解法 30 题,最近一段时间会频繁更新)可以点击下方链接,直达gitbook:
https://codernie.gitbooks.io/leetcode-solutions/content/
也欢迎大家关注我的同名微信公众号(大雄的学习人生),有问题可以随时后台回复我,多多探讨。

21. Merge Two Sorted Lists

解题思路

从头至尾一起遍历,每次比较头结点的大小,先添加小的结点,直到某一个链表为空为止,最后再将另一个还不为空的链表添加到末尾即可。

实现代码:

// 21. Merge Two Sorted Lists
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
  ListNode* head = new ListNode(0);
  ListNode* p = head;
  while (l1 != NULL && l2 != NULL) {
    if (l1->val < l2 ->val) {
      p->next = l1;
      l1 = l1->next;
    } else {
      p->next = l2;
      l2 = l2->next;
    }
    p = p->next;
  }
  if (l1 != NULL) {
    p->next = l1;
  } else if (l2 != NULL) {
    p->next = l2;
  }
  return head->next;
}

22. Generate Parentheses

解题思路

回溯法,用一个数组记录【当前字符串,左括号数量,右括号数量】,做 2n 次循环,每次循环遍历当前数组,对左括号不足 n 的添加左括号,右括号不足左括号的添加右括号(确保parentheses是有效的)

实现代码:

// 22. Generate Parentheses O(2^N)
vector<string> generateParenthesis(int n) {
  if (n == 0) return {};
  // nowString, leftCount, rightCount
  vector<pair<string, pair<int, int> > > res = {{"", {0, 0}}};
  vector<pair<string, pair<int, int> > > linshi;
  for (int i = 0; i < 2 * n; i++) {
    for (int j = 0; j < res.size(); j++) {
      string oldString = res[j].first;
      if (res[j].second.first < n) {
        linshi.push_back({oldString + '(', {res[j].second.first + 1, res[j].second.second}});
      }
      if (res[j].second.first > res[j].second.second) {
        linshi.push_back({oldString + ')', {res[j].second.first, res[j].second.second + 1}});
      }
    }
    res = linshi;
    linshi = {};
  }
  vector<string> result;
  for (int i = 0; i < res.size(); i++) {
    result.push_back(res[i].first);
  }
  return result;
}

23. Merge k Sorted Lists

解题思路

考虑使用优先队列,这道题相当于21题的加强版,但是如果单纯使用21题的每轮比较法,那么每轮只要要比较 k 次,而我们知道这其中肯定会存在很多重复的比较,所以我们可以使用二叉堆来保存每一轮的比较信息,而在C++中STL已经为我们实现了这种数据结构,那就是优先队列priority_queue,直接调用即可。

实现代码:

// 23. Merge k Sorted Lists
ListNode* mergeKLists(vector<ListNode*>& lists) {
  ListNode *dump = new ListNode(0), *p = dump, *nowPoint;
  // val, node
  priority_queue<pair<int, ListNode*>, vector<pair<int, ListNode*>>, greater<pair<int, ListNode*>> > pQueue;
  for (ListNode* node : lists) {
    if (node != NULL)
      pQueue.push({node->val, node});
  }
  while (!pQueue.empty()) {
    nowPoint = pQueue.top().second;
    p->next = nowPoint;
    p = p->next;
    pQueue.pop();
    if (nowPoint->next != NULL) {
      pQueue.push({nowPoint->next->val, nowPoint->next});
    }
  }
  return dump->next;
}

24. Swap Nodes in Pairs

解题思路

两两交换即可,这里每轮需要变换三个指向,第一个的next要指向第二个的next,第二个的next要指向第一个,前驱的next要指向第二个,每轮过后让 p 指向第一个即可。

实现代码:

// 24. Swap Nodes in Pairs
ListNode* swapPairs(ListNode* head) {
  ListNode *dump = new ListNode(0), *p = dump, *first, *second;
  dump->next = head;
  while (p != NULL && p->next != NULL && p->next->next != NULL) {
    first = p->next;
    second = p->next->next;
    first->next = second->next;
    second->next = first;
    p->next = second;
    p = first;
  }
  return dump->next; 
}

25. Reverse Nodes in k-Group

解题思路

这题相当于24题的加强版,将24题中的2变成了k,换汤不换药,稍微复杂一点,所以建议把每一步的思路理清楚,不然很容易弄错,大的思路是先检查是否还剩下足够数量的结点;然后进行反向操作:反向操作分为三步,中间结点关系反向,头结点指向处理 和 尾结点指向处理;最后让 p 结点后挪即可。

实现代码:

// 25. Reverse Nodes in k-Group
ListNode* reverseKGroup(ListNode* head, int k) {
  if (k == 1) return head;
  vector<ListNode*> nodeList;
  ListNode *dump = new ListNode(0), *p = dump, *former, *latter, *nextLatter, *q;
  dump->next = head;
  while (true) {
    // check count of left nodes
    q = p;
    for (int i = 0; i < k + 1; i++) {
      if (q != NULL) {
        q = q->next;
      } else {
        return dump->next;      
      }
    }
    // deal with medial relations 
    former = p->next;
    latter = former->next;
    for (int i = 0; i < k - 1; i++) {
      // save latter->next as next latter
      nextLatter = latter->next;
      // latter->next = former
      latter->next = former;
      // save latter as next former  
      former = latter;
      latter = nextLatter;
    }
    // deal with head
    q = p->next;
    p->next = former;
    // deal with tail
    q->next = latter;

    p = q;
  }
  return dump->next;      
}

26. Remove Duplicates from Sorted Array

解题思路

按照题意,把数组中不重复出现的数字保存在数组前端即可。

实现代码:

// 26. Remove Duplicates from Sorted Array
int removeDuplicates(vector<int>& nums) {
  if (nums.size() == 0) return 0;
  int j = 1;
  for (int i = 1; i < nums.size(); i++) {
    if (nums[i] != nums[i - 1]) {
      nums[j++] = nums[i];
    }
  } 
  return j;      
}

27. Remove Element

解题思路

这道题和 26 题几乎没有区别,把判断条件稍微改变即可。

实现代码:

// 27. Remove Element
int removeElement(vector<int>& nums, int val) {
  int j = 0;
  for (int i = 0; i < nums.size(); i++) {
    if (nums[i] != val) {
      nums[j++] = nums[i];
    }
  }
  return j;
}

28. Implement strStr()

解题思路

字符串匹配算法,选择比较多,最容易的就是暴力匹配,高阶一点可以使用KMP,这里简单起见,采用暴力解法,嘻嘻。

实现代码:

// 28. Implement strStr()
int strStr(string haystack, string needle) {
  if (needle.size() == 0) return 0;
  int hLen = haystack.size(), nLen = needle.size();
  for (int i = 0; i < hLen; i++) {
    if (hLen - i < nLen) {
      return -1;
    } else {
      if (haystack.substr(i, nLen).compare(needle) == 0) {
        return i;
      }
    }
  }
  return -1;
}

29. Devide Two Integers

解题思路

这道题如果用减法去实现会造成时间复杂度过高,从而导致时间溢出,所以这里采用位运算法,从 2 的 31 次方开始除,一直除到 2 的 0 次方,为了防止内存溢出,我们把结果和中间变量保存在 long 类型中的。

实现代码:

// 29. Divide Two Integers
int divide(int dividend, int divisor) {
  if (dividend > INT32_MAX || dividend < INT32_MIN || divisor > INT32_MAX || divisor < INT32_MIN) return INT32_MAX;
  long son = abs((long)divisor), father = abs((long)dividend), res = 0, base = 1, sum = 0;
  for (int i = 31; i >= 0; i--) {
    if (sum + (son << i) <= father) {
      sum += son << i;
      res += base << i;
    }
  }
  if ((dividend >= 0 && divisor > 0) || (dividend < 0 && divisor < 0)) {
    return (res > INT32_MAX) ? INT32_MAX : res;
  } else {
    return (-res < INT32_MIN) ? INT32_MAX : -res;
  }
}

30. Substring with Concatenation of All Words

解题思路

使用哈希表存储 words 数组中各个单词的数量,然后使用暴力匹配即可。

实现代码:

// 30. Substring with Concatenation of All Words
vector<int> findSubstring(string s, vector<string>& words) {
  vector<int> result;
  if (words.size() == 0 || words[0].size() == 0 || s.size() < words.size() * words[0].size()) {
    return result;
  }
  unordered_map<string, int> counts;
  for (string word : words) {
    if (counts.find(word) == counts.end()) {
      counts[word] = 1;
    } else {
      counts[word]++;
    }
  }
  int wordCount = words.size(), wordLength = words[0].size(), strLength = s.size();
  for (int i = 0; i <= strLength - wordLength * wordCount; i++) {
    unordered_map<string, int> innerCounts = counts;
    bool flag = true;
    for (int j = 0; j < wordCount; j++) {
      string nowStr = s.substr(i + j * wordLength, wordLength);
      if (innerCounts.find(nowStr) == innerCounts.end() || innerCounts[nowStr] == 0) {
        flag = false;
        break;
      } else {
        innerCounts[nowStr]--;
      }
    }
    if (flag) {
      result.push_back(i);
    }
  }
  return result;
}
    原文作者:大雄的学习人生
    原文地址: https://www.jianshu.com/p/b2c006dbff61
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞