赞
踩
前言:由于开题好长时间不刷题了,中级的算法题好难啊!一定要再次坚持下去。
1)题目:
2)解析:排序+双指针(参考官方解法)
注意关键词:不重复。要用三重循环来解决此题
如果我们固定了前两重循环枚举到的元素 a 和 b,那么只有唯一的 c 满足 a+b+c=0。当第二重循环往后枚举一个元素 b’时,由于 b’ > b,那么满足 a+b’+c’=0的 c’ 一定有 c’ < c,即 c’在数组中一定出现在 c的左侧。也就是说,我们可以从小到大枚举 b,同时从大到小枚举 c,即第二重循环和第三重循环实际上是并列的关系。
有了这样的发现,我们就可以保持第二重循环不变,而将第三重循环变成一个从数组最右端开始向左移动的指针,
这个方法就是我们常说的「双指针」,当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O(N^2) 减少至 O(N)。
总结:
a、把数组排序
b、三重循环,第一重从0 到length-2 遍历数组;第二重从i+1开始遍历;第三重从length-1开始遍历
c、比较nums[left] + nums[right] 与 nums[i]的关系;若相等,推入数组;若小于,left指针右移;若大于,right指针左移。
d、注意两次“去重”,第一次是三重循环,第二重从小到大,第三重从大到小;第二次,若比较相等left右移找到不等的值,right左移。
vector<vector<int>> threeSum(vector<int>& nums) { sort(nums.begin(), nums.end()); int length = nums.size(); vector<vector<int>> vec; for (int i = 0; i < length-2; i++) { //过滤重复 if (i > 0 && nums[i] == nums[i - 1]) continue; //第二重循环从小到大枚举 int left = i + 1; //第三重循环从大到小枚举 int right = length - 1; while (left < right) { if (nums[left] + nums[right] + nums[i] == 0) { vec.push_back(vector<int>()); vec.back().push_back(nums[left]); vec.back().push_back(nums[right]); vec.back().push_back(nums[i]); //过滤重复 //注意left与他的下一个left+1比较,不是前一个left-1哦,刚开始想的left-1一直不对, while (left < right && nums[left] == nums[left + 1]) { left++; } //过滤重复 //注意right与他的上一个right-1比较,不是前一个right+1哦,刚开始想的right+1一直不对, while (left < right && nums[right] == nums[right - 1]) { right--; } left++; right--; } //因为已经排序过了,(-nums[i])作为目标元素,如果nums[left] + nums[right]小于目标值,让left向右移动就会变大 else if (nums[left] + nums[right] < (-nums[i])) { left++; } else { //right向左移动相加值就会变小 right--; } } } return vec; }
1)题目: 给定一个n*n的矩阵,如果一个元素为0,则将其所在行和列的元素都设为0,请使用“原地”算法。
2)思路一:
第一次循环把是0的位置的行数和列数存在队列里,(pair可以把两个元素合并成一个元素);第二次循环把对应元素置0。
//空间复杂度:O(m + n) void setZeroes2(vector<vector<int>>& matrix) { //第一次遍历把0元素的行和列存在队列中 //pair 将两个数据组合成一个数据 //当一个函数需要两个数据时,也可以用pair queue<pair<int, int>> rot; //行数 int line = matrix.size(); //列数 int row = matrix[0].size(); for (int i = 0; i < line; i++) { for (int j = 0; j < row; j++) { if (matrix[i][j] == 0) rot.emplace(i, j); } } //第二次遍历把对应元素改为0 while (!rot.empty()) { //行 int h = rot.front().first; //列 int l = rot.front().second; rot.pop(); for (int i = 0; i < line; i++) { matrix[i][l] = 0; } for (int j = 0; j < row; j++) { matrix[h][j] = 0; } } }
3)思路二:
用两个标记数组代替思路一的队列,一个标记行,一个标记列。如果元素为0,就把元素对应的行数和列数标记为true。第二次遍历,再把行数或列数为true的元素置0。
//空间复杂度:O(m + n) //用两个标记数组代替下面的队列 void setZeroes3(vector<vector<int>>& matrix) { int m = matrix.size(); int n = matrix[0].size(); vector<int> row(m), col(n); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (!matrix[i][j]) { row[i] = col[j] = true; } } } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (row[i] || col[j]) { matrix[i][j] = 0; } } } }
4)思路三:
把矩阵的第一行和第一列作为标记数组。
第一次遍历所有元素,如果matrix[i][j]是0就把第一行第一列对应的行和列(matrix[i][0] matrix[0][j])置0,如果第一行或第一列原本就有0,用flag_row或flag_col标记一下。
第二次遍历除第一行第一列以外的其他元素,如果他所在对应的matrix[i][0] matrix[0][j]为0,则把该元素置为0。
如果第一行或第一列原本就有0(flag_row或flag_col为true),就把第一行或第一列置0。
//我们可以用矩阵的第一行和第一列代替方法一中的两个标记数组,以达到 O(1) 的额外空间。 void setZeroes(vector<vector<int>>& matrix) { int m = matrix.size(); int n = matrix[0].size(); //标记第一行是否有数字0 int flag_row = false; //标记第一列是否有数字0 int flag_col = false; for (int i = 0; i < m; i++) { //如果matrix[i][j]是0就把第一行第一列对应的行和列(matrix[i][0] matrix[0][j])置0 for (int j = 0; j < n; j++) { if (matrix[i][j] == 0) { //如果第一行或第一列原本就有0,标记一下 if (i == 0) flag_row = true; if (j == 0) flag_col = true; matrix[i][0] = matrix[0][j] = 0; } } } //遍历除第一行第一列以外的其他元素,如果他所在对应的matrix[i][0] matrix[0][j]为0,则把该元素置为0 for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { if (!matrix[i][0] || !matrix[0][j]) { matrix[i][j] = 0; } } } //如果第一行原本就有0,就把第一行置0 if (flag_row) { for (int i = 0; i < n; i++) { matrix[0][i] = 0; } } //如果第一列原本就有0,就把第一列置0 if (flag_col) { for (int j = 0; j < m; j++) { matrix[j][0] = 0; } } }
1)题目:
2)思路:由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定是相同的,故可以将排序之后的字符串作为哈希表的键。
vector<vector<string>> groupAnagrams(vector<string>& strs) { //对字符串数组中的每一个字符串排序,排序结果相同的就是字母异位词 //键值是源字符串,键是排序后的字符串。妙妙妙!!! //unordered_map 哈希映射 无序 map是有序容器 unordered_map<string, vector<string>> stp; vector<vector<string>> res; int length = strs.size(); //注意是 string& 不是 string for (string& str : strs) { string key = str; sort(key.begin(), key.end()); stp[key].emplace_back(str);//stp[key]表示key这组异位词的集合 } //unordered_map 前向迭代期 for (auto it = stp.begin(); it != stp.end(); ++it) { res.emplace_back(it->second);//遍历哈希表,将字母异位词的集合存入结果集中 } return res; }
1)题目:
2)思路:
定义一个bool型的标记数组str[255],字母s[i]出现以后,对应的str[s[i]]=true;当字母再次出现时,追溯到第一次出现的位置j,无重复字符的长度就是 i-j+1;
int lengthOfLongestSubstring(string s) { //双指针,i指针是遍历字符串,j指针回溯 int length = s.size(); bool nums[255] = { false }; int j = 0,maxnum=0; for (int i = 0; i < length; i++) { //2、访问到重复字符,将其推移,直到找到第一个重复字符 while (nums[s[i]]) { nums[s[j]] = false; ++j; } //1、每访问一个新的字符,就将其进行标记 nums[s[i]] = true; maxnum = max(maxnum, i - j + 1); } return maxnum; }
1)题目:
2)思路:中心扩散法
回文子串中心互为镜像,因此,回文可以从他的中心展开,奇数有一个中心点,偶数有两个中心点。
//最长回文串(毫无头绪啊!!!!!!) //中心扩展法 string longestPalindrome(string s) { int length = s.size(); if (length < 2) return s; int start = 0;//最长回文串起始位置 int end = 0;//终止位置 int maxlength = 0; for (int i = 0; i < length; i++) { int odd = strlength(s, i, i);//奇数中心 int even = strlength(s, i, i + 1);//偶数中心 maxlength = max(max(odd, even), maxlength); if (maxlength > (end - start + 1)) { start = i - (maxlength - 1) / 2; end = i + maxlength / 2; } } return s.substr(start, maxlength); //该函数的意思是获取从start开始长度为mlen长 度的字符串 } //以left和right为中心的回文串长度 int strlength(string s, int left, int right) { //注意left可以取到0 while ( left>=0 && right<s.size() && s[left] == s[right]) { left--; right++; } return right - left - 1;//返回回文子串的长度 }
1)题目:
2)思路:注意一点三元子序列只要是递增的就可以,可以连续也可以不连续。
定义两个变量,一个用来存储最小值,一个用来存储中间值,注意他们的初始值是 INT_MAX。扫描数组:
如果当前数字小于等于最小值,就更新最小值;
如果当前数字大于最小值但小于等于中间值,就更新中间值;
如果当前值既不小于等于最小值也不小于等于中间值,则说明找到了三个递增子序列,直接返回true。
//子序列可以不连续 bool increasingTriplet(vector<int>& nums) { int length = nums.size(); if (length < 3) return false; int minnum = INT_MAX, mid = INT_MAX; for (int i = 0; i < length; i++) { if (nums[i] <= minnum) minnum = nums[i]; else if (nums[i] <= mid ) { mid = nums[i]; } else { return true; } } return false; }
1)题目:
2)错误思路:
刚开始想的是先把两个链表转化成对应的数字,然后两个数字相加计算出和,最后把和转化成链表输出。由于长时间不做链表的题导致都不会常见链表了,废了好多时间。而且这种方法还没有通过测试,因为两数之和超出了INT的范围。
3)正确思路:
由于输入的两个链表都是 逆序 存储数字的位数的,因此两个链表中同一位置的数字可以直接相加。
我们同时遍历这两个链表,逐位计算他们的和,并与当前位置的进位值相加。
此处有两点注意:a、最终结果也是逆序存储 b、进位值的处理
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { //创建list链表存出结果 ListNode* list = new ListNode; //ptr是临时链表,存储每一位数字,再把他插入list ListNode* ptr = list; int number = 0; while (number || l1!=nullptr || l2 != nullptr) { if (l1 != nullptr) { number += l1->val; l1 = l1->next; } if (l2 != nullptr) { number += l2->val; l2 = l2->next; } //number % 10只取末位数字存入链表 //在list末尾插入数据,保证最终结果也是逆序存储(最低位是个位) ptr->next = new ListNode(number % 10, nullptr); ptr = ptr->next; //进位值 number = number / 10; } return list->next; }
1)题目:
2)思路: 双指针(链表经常应用双指针)——参考“数据结构和算法”的解题思路
一个指针把奇数位置串联起来,另一个指针把偶数位置连起来,再把偶数位置链表头部接到奇数位置指针尾部。
所以需要定义几个变量:
记录奇数位置的头部指针:oddhead
奇数位置扫描指针:oddcur
记录偶数位置的头部指针:evenhead
偶数位置扫描指针:evencur
ListNode* oddEvenList(ListNode* head) { if (head == nullptr || head->next == nullptr) { return head; } //分别记录奇数链表的头节点和尾节点,偶数链表的头节点和尾节点 //奇数链表的头结点 ListNode* odd = head; //奇数链表的当前节点 ListNode* oddcur = odd; //偶数链表的头结点 ListNode* even = head->next; //偶数链表的当前节点 ListNode* evencur = even; while (evencur !=nullptr && evencur->next!=nullptr)//注意这是偶数当前节点,不能是奇数;因为偶数在奇数后面 { //奇数节点串在一起 oddcur->next = oddcur->next->next; //偶数节点串在一起 evencur->next = evencur->next->next; //奇偶指针往后移动 oddcur = oddcur->next; evencur = evencur->next; } //最后把偶数链表的头部串在奇数链表的尾部 oddcur->next = even; //返回奇数指针的头部 return odd; }
1)题目:
2)思路:这道题看起来说的很多,很复杂是的,其实就是找两个链表的交点。我首先想到的是先遍历一个链表,并把它的值存入哈希表中,再遍历另一个链表,如果哈希表中存在此链表中的当前值,当前值就是两个链表的焦点。但是哈希表存两个值,此处只需要保存一个值就够了,用哈希表浪费空间。参考评论区,选用的是集合不是哈希表。
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) { //把headA链表遍历一遍,把值存在集合中;再遍历headB链表,若果在集合中存在headB中的值就返回该值 set<ListNode*> mac; while (headA !=nullptr) { mac.insert(headA); headA = headA->next; } while (headB !=nullptr) { if (mac.count(headB)) { return headB; } headB = headB->next; } return nullptr; }
3)思路2:先计算两个链表的长度,如果两个链表的长度不相等就让长的先走,直到两个链表一样长了再同时走。同时比较当前节点是否相同,如果不同就同时继续往后移动。
//计算链表长度 int length(ListNode* head) { int n = 0; while (head!=nullptr) { head = head->next; n++; } return n; } ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) { int length1 = length(headA); int length2 = length(headB); //如果两个链表长度不相等,长度长的先走 while (length1!=length2) { if (length1 > length2) { headA = headA->next; length1--; } else { headB = headB->next; length2--; } } //两个长度相等了再开始比较 while (headA!=headB) { headA = headA->next; headB = headB->next; } //走到最后,最终会有两种可能,一种是headA为空, //也就是说他们俩不相交。还有一种可能就是headA //不为空,也就是说headA就是他们的交点 return headA; }
4)思路:双指针——参考“数据结构与算法”解法
假设A有5个节点,B有8个节点
因为A和B的总节点数是不变的13,A指针把A链表走完以后就开始从B链表头部开始走,同时B指针把B链表走完了就开始从A链表头部开始走。如果A链表和B链表相交,A、B指针肯定会在相交点相遇,因为到相交点出A、B指针走过的路一样长,都刚好吧整个链表 完整的走了 一遍。
下面这条评论简洁又有趣的概括了这种思路:
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) { //双指针:A指针如果把A链表走完了,然后就从B链表开始走;B指针如果把B链表走完了就从A开始走; //如此循环,只要A和B链表相交,总会相遇 ListNode* listA = headA; ListNode* listB = headB; while (listA!=listB) { //如果指针listA不为空,listA就往后移一步。 //如果指针listA为空,就让指针listA指向headB(注意这里是headB不是listB) listA = listA == nullptr ? headB : listA->next; listB = listB == nullptr ? headA : listB->next; } //listA要么是空,要么是两链表的交点 return listA; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。