题目:两数相加
-
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
-
-
由于输入的两个链表都是逆序存储数字的位数的,因此两个链表中同一位置的数字可以直接相加。同时遍历两个链表,逐位计算它们的和,并与当前位置的进位值相加。具体而言,如果当前两个链表处相应位置的数字为 n1,n2,进位值为 carry,则它们的和为 n1+n2+carry;其中,答案链表处相应位置的数字为 (n1+n2+carry) mod 10。如果两个链表的长度不同,则可以认为长度短的链表的后面有若干个 0 。如果两个链表的长度不同,则可以认为长度短的链表的后面有若干个 0 。
-
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { ListNode *head=nullptr,*tail=nullptr; int carry=0; while(l1 || l2){ int n1 = l1?l1->val:0; int n2 = l2?l2->val:0; int sum = n1+n2+carry; if(!head){ head = tail = new ListNode(sum%10); }else{ tail->next=new ListNode(sum%10); tail=tail->next; } carry=sum/10; if(l1){ l1=l1->next; } if(l2){ l2=l2->next; } } if(carry>0){ tail->next=new ListNode(carry); } return head; } };
-
时间复杂度:O(max(m,n)),其中 m 和 n 分别为两个链表的长度。我们要遍历两个链表的全部位置,而处理每个位置只需要 O(1) 的时间。空间复杂度:O(1)。注意返回值不计入空间复杂度。
题目:无重复字符的最长子串
-
给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串 的长度。 -
其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!我们只要把队列的左边的元素移出就行了,直到满足题目要求!一直维持这样的队列,找出队列出现最长的长度时候,求出解!
-
class Solution { public: int lengthOfLongestSubstring(string s) { if(s.size()==0){ return 0; } unordered_set<char> temp; int max_one=0; int left=0; for(int i=0;i<s.size();i++){ while(temp.find(s[i])!=temp.end()){ temp.erase(s[left]); left++; } max_one = max(max_one,i-left+1); temp.insert(s[i]); } return max_one; } };
-
unordered_set 容器,可直译为“无序 set 容器”,即 unordered_set 容器和 set 容器很像,唯一的区别就在于 set 容器会自行对存储的数据进行排序,而 unordered_set 容器不会。无序集合(unordered_set)是一种使用哈希表实现的无序关联容器,其中键被哈希到哈希表的索引位置,因此插入操作总是随机的。无序集合上的所有操作在平均情况下都具有常数时间复杂度O(1),但在最坏情况下,时间复杂度可以达到线性时间O(n),这取决于内部使用的哈希函数,但实际上它们表现非常出色,通常提供常数时间的查找操作。
题目:寻找两个正序数组的中位数
-
给定两个大小分别为
m
和n
的正序(从小到大)数组nums1
和nums2
。请你找出并返回这两个正序数组的 中位数 。 -
暴力求解:使用归并的方式,合并两个有序数组,得到一个大的有序数组。大的有序数组的中间位置的元素,即为中位数。时间复杂度是 O(m+n),空间复杂度是 O(m+n)。
-
不需要合并两个有序数组,只要找到中位数的位置即可。由于两个数组的长度已知,因此中位数对应的两个数组的下标之和也是已知的。维护两个指针,初始时分别指向两个数组的下标 000 的位置,每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。空间复杂度降到 O(1),但是时间复杂度仍是 O(m+n)。
-
如果对时间复杂度的要求有 log,通常都需要用到二分查找,这道题也可以通过二分查找实现。根据中位数的定义,当 m+n 是奇数时,中位数是两个有序数组中的第 (m+n)/2 个元素,当 m+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2 个元素和第 (m+n)/2+1 个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 k 小的数,其中 k 为 (m+n)/2 或 (m+n)/2+1。
-
class Solution { public: int help(const vector<int> &nums1,const vector<int> &nums2,int k){ int m=nums1.size(); int n=nums2.size(); int index1=0,index2=0; while(true){ if(index1==m){ return nums2[index2+k-1]; } if(index2==n){ return nums1[index1+k-1]; } if(k==1){ return min(nums1[index1],nums2[index2]); } int newIndex1=min(index1+k/2-1,m-1); int newindex2=min(index2+k/2-1,n-1); int p1=nums1[newIndex1]; int p2=nums2[newindex2]; if(p1<=p2){ k-=newIndex1-index1+1; index1=newIndex1+1; }else{ k-=newindex2-index2+1; index2=newindex2+1; } } } double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { int len=nums1.size()+nums2.size(); if(len&1){ return help(nums1,nums2,(len+1)/2); }else{ return (help(nums1,nums2,len/2)+help(nums1,nums2,len/2+1))/2.0; } } };
-
主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较 (这里的 “/” 表示整除) , nums1 中小于等于 pivot1 的元素有 nums1[0 … k/2-2] 共计 k/2-1 个;nums2 中小于等于 pivot2 的元素有 nums2[0 … k/2-2] 共计 k/2-1 个;取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个。 这样 pivot 本身最大也只能是第 k-1 小的元素;如果 pivot = pivot1,那么 nums1[0 … k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums1 数组; 如果 pivot = pivot2,那么 nums2[0 … k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums2 数组;由于我们 “删除” 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数。
题目:最长回文子串
-
给你一个字符串
s
,找到s
中最长的回文子串。如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。 -
对于一个子串而言,如果它是回文串,并且长度大于 222,那么将它首尾的两个字母去除之后,它仍然是个回文串。例如对于字符串 “ababa”,如果我们已经知道 “bab”是回文串,那么 “ababa”一定是回文串,这是因为它的首尾两个字母都是 “a”。
-
根据这样的思路,我们就可以用动态规划的方法解决本题。我们用 P(i,j) 表示字符串 s 的第 i 到 j 个字母组成的串(下文表示成 s[i:j])是否为回文串。那么我们就可以写出动态规划的状态转移方程:
-
P ( i , j ) = P ( i + 1 , j − 1 ) ∧ ( S i = = S j ) P(i,j)=P(i+1,j−1)∧(S_i ==S_j) P(i,j)=P(i+1,j−1)∧(Si==Sj)
-
只有 s[i+1:j−1] 是回文串,并且 s 的第 i 和 j 个字母相同时,s[i:j] 才会是回文串。
-
-
class Solution { public: string longestPalindrome(string s) { int n=s.size(); if(n<2){ return s; } int res=1; int begin=0; vector<vector<bool>> dp(n,vector<bool>(n)); for(int i=0;i<n;i++){ dp[i][i]=true; } for(int L=2;L<=n;L++){ for(int i=0;i<n;i++){ int j=L+i-1;// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得 if(j>=n){ break; } if(s[i]!=s[j]){ dp[i][j]=false; }else{ if(j-i<3){ dp[i][j]=true; }else{ dp[i][j]=dp[i+1][j-1]; } } if(dp[i][j] && j-i+1>res){ res=j-i+1; begin=i; } } } return s.substr(begin,res); } };
题目:N 字形变换
-
将一个给定字符串
s
根据给定的行数numRows
,以从上往下、从左到右进行 Z 字形排列。比如输入字符串为"PAYPALISHIRING"
行数为3
时,排列如下:-
P A H N A P L S I I G Y I R
-
-
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:
"PAHNAPLSIIGYIR"
。请你实现这个将字符串进行指定行数变换的函数:string convert(string s, int numRows); -
设 n 为字符串 s 的长度,r=numRows。对于 r=1(只有一行)或者 r≥n(只有一列)的情况,答案与 s 相同,我们可以直接返回 s。对于其余情况,考虑创建一个二维矩阵,然后在矩阵上按 Z 字形填写字符串 s,最后逐行扫描矩阵中的非空字符,组成答案。
-
根据题意,当我们在矩阵上填写字符时,会向下填写 r 个字符,然后向右上继续填写 r−2 个字符,最后回到第一行,因此 Z 字形变换的周期 t=r+r−2=2r−2,每个周期会占用矩阵上的 1+r−2=r−1 列。
-
因此我们有 ⌈ n t ⌉ \Big\lceil\dfrac{n}{t}\Big\rceil ⌈tn⌉个周期(最后一个周期视作完整周期),乘上每个周期的列数,得到矩阵的列数 c = ⌈ n t ⌉ ⋅ ( r − 1 ) c=\Big\lceil\dfrac{n}{t}\Big\rceil\cdot(r-1) c=⌈tn⌉⋅(r−1)。创建一个 r 行 c 列的矩阵,然后遍历字符串 s 并按 Z 字形填写。具体来说,设当前填写的位置为 (x,y),即矩阵的 x 行 y 列。初始 (x,y)=(0,0),即矩阵左上角。若当前字符下标 i 满足 i m o d t < r − 1 i\bmod t<r-1 imodt<r−1,则向下移动,否则向右上移动。
-
class Solution { public: string convert(string s, int numRows) { int n=s.size(),r=numRows; if(r==1||r>=n){ return s; } int t=r*2-2; int c=(n+t-1)/(t)*(t-1); vector<string> temp(r,string(c,0)); for(int i=0,x=0,y=0;i<n;i++){ temp[x][y]=s[i]; if(i%t<r-1){ x++; }else{ x--; y++; } } string res; for(auto &row:temp){ for(char ch:row){ if(ch){ res+=ch; } } } return res; } };