https://leetcode-cn.com/problems/two-sum/
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
哈希表:unordered_map O(n), O(n)
https://leetcode-cn.com/problems/add-two-numbers/
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
链表两数相加:设置dummy,cur,进位carry
https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
滑动窗口
https://leetcode-cn.com/problems/median-of-two-sorted-arrays/
两个排序数组的合并
5:最长回文子串
动态规划://dp为size*size大小的矩阵,dp[i][j]表示以s[i]开头,s[j]结尾的回文串长度(如果不是回文串,则为0)
if (dp[i + 1][j - 1]>0) {
dp[i][j] = dp[i + 1][j - 1] + 2;
}
else dp[i][j] = 0;
6. Z 字形变换
按行保存:找规律,第一行、最后一行和中间行的规律不同
7.整数反转
res = res*10 + b;
注意越界问题
8. 字符串转换整数 (atoi)
stringstream ss;
ss<
ss>>n;
return n;
9:回文数
bool isPalindrome(int x) {
if(x<0) return false;
int tmp = x;
int res = 0;
while(tmp!=0){
int rem = tmp%10;
if ((res > INT_MAX / 10) || ((res == INT_MAX) && (rem > 7)))
return false;
res = res*10+rem;
tmp/=10;
}
return x == res;
}
10. 正则表达式匹配
动态规划
vector
11.盛最多水的容器
头尾两个指针,小的一边--
最初我们考虑由最外围两条线段构成的区域。现在,为了使面积最大化,我们需要考虑更长的两条线段之间的区域。如果我们试图将指向较长线段的指针向内侧移动,矩形区域的面积将受限于较短的线段而不会获得任何增加。但是,在同样的条件下,移动指向较短线段的指针尽管造成了矩形宽度的减小,但却可能会有助于面积的增大。因为移动较短线段的指针会得到一条相对较长的线段,这可以克服由宽度减小而引起的面积减小。
42. 接雨水
从左扫一遍,从右扫一遍
class Solution {
public:
int trap(vector& height) {
if(height.size() == 0) return 0;
int n = height.size();
// left[i]表示i左边的最大值,right[i]表示i右边的最大值
vector left(n), right(n);
for (int i = 1; i < n; i++) {
left[i] = max(left[i - 1], height[i - 1]);
}
for (int i = n - 2; i >= 0; i--) {
right[i] = max(right[i + 1], height[i + 1]);
}
int water = 0;
for (int i = 0; i < n; i++) {
int level = min(left[i], right[i]);
water += max(0, level - height[i]);
}
return water;
}
};
12. 整数转罗马数字
string intToRoman(int num) {
//哈希表
vector value = { 1000,900,500,400,100,90,50,40,10,9,5,4,1 };
vector dic = { "M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I" };
string str;
int i = 0;
while (num > 0 && i < dic.size()) {
if (num >= value[i]) {
str += dic[i];
num -= value[i];
}
else
i++;
}
return str;
}
13:罗马数字转整数
第一次遍历一个字符把符合值加到结果里,第二次遍历两个字符从结果中减去符合值。
14. 最长公共前缀
先找到最短长度,然后看这个长度内是否字符否相等
15. 三数之和
先将数组进行排序
从左侧开始,选定一个值为 定值 ,右侧进行求解,获取与其相加为 00 的两个值
类似于快排,定义首和尾
首尾与 定值 相加
等于 00,记录这三个值
小于 00,首部右移
大于 00,尾部左移
定值右移,重复该步骤
var threeSum = function(nums) {
// 最左侧值为定值,右侧所有值进行两边推进计算
let res = [];
nums.sort((a, b) => a - b);
let size = nums.length;
if (nums[0] <= 0 && nums[size - 1] >= 0) {
// 保证有正数负数
let i = 0;
while (i < size - 2) {
if (nums[i] > 0) break; // 最左侧大于0,无解
let first = i + 1;
let last = size - 1;
while (first < last) {
if (nums[i] * nums[last] > 0) break; // 三数同符号,无解
let sum = nums[i] + nums[first] + nums[last];
if (sum === 0) {
res.push([nums[i], nums[first], nums[last]]);
}
if (sum <= 0) {
// 负数过小,first右移
while (nums[first] === nums[++first]) {} // 重复值跳过
} else {
while (nums[last] === nums[--last]) {} // 重复值跳过
}
}
while (nums[i] === nums[++i]) {}
}
}
16. 最接近的三数之和
和15一样的思路:排序+双指针
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int ans = nums[0] + nums[1] + nums[2];
for(int i=0;i target)
end--;
else if(sum < target)
start++;
else
return ans;
}
}
return ans;
}
18. 四数之和
与16类似,
先将数组排序固定两个元素在用两个指针,一个指向头,一个指向尾,看四数之和为多少,太大了右指针左移,太小了左指针右移,因为有可能存在重复的数组,先将结果保存在set中,最后在转为list输出.
class Solution {
public:
vector> fourSum(vector& nums, int target) {
if(nums.size()<4) return {};
vector> res;
sort(nums.begin(), nums.end());
if (nums.empty()) return {};
for (int k = 0; k < nums.size()-3; ++k) {
if (k > 0 && nums[k] == nums[k - 1]) continue;
int ntarget = target - nums[k];
for (int j = k+1; j < nums.size()-2; ++j) {
if (j > k+1 && nums[j] == nums[j - 1]) continue;
int nntarget = ntarget - nums[j];
int m = j + 1, n = nums.size() - 1;
while (m < n) {
if (nums[m] + nums[n] == nntarget) {
res.push_back({nums[k], nums[j], nums[m], nums[n]});
while (m < n && nums[m] == nums[m + 1]) ++m;
while (m < n && nums[n] == nums[n - 1]) --n;
++m; --n;
} else if (nums[m] + nums[n] < nntarget) ++m;
else --n;
}
}
}
return res;
}
};
17. 电话号码的字母组合
回溯法
class Solution {
public:
vector res;
const string mymap[10] = {
" ", //0
"", //1
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz"
};
vector letterCombinations(string digits) {
if(digits.size() == 0) return res;
string p = "";
dfs(digits, 0, p);
return res;
}
void dfs(string &digits, int index,const string &p)
{
if(p.size() == digits.size()) {
res.push_back(p);
return;
}
string number = mymap[digits[index]-'0'];
for(int i=0; i
19. 删除链表的倒数第N个节点
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(n<=0) return head;
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* first = head;
for(int i=0;inext;
else return head;
}
ListNode* second = head;
ListNode* preSecond = dummy;
//找到第n个节点
while(first){
first = first->next;
preSecond = second;
second = second->next;
}
//删除节点
preSecond->next = second->next;
return dummy->next;
}
20. 有效的括号
栈
22. 括号生成
回溯:很巧妙的思路
23. 合并K个排序链表
归并的思想
25. K 个一组翻转链表
用栈
ListNode* reverseKGroup(ListNode* head, int k) {
if(head == NULL) return NULL;
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* cur = dummy;
ListNode* changeCur = head;
int count = k;
stack ss;
while(true)
{
count = k;
ListNode* changePre = changeCur;
while(count!=0 && changeCur != NULL){
ss.push(changeCur);
changeCur = changeCur->next;
count--;
}
cout << count << endl;
if(count!=0){
cur->next = changePre;
break;
}
while(!ss.empty()){
cur->next = ss.top();
ss.pop();
cur = cur->next;
}
}
return dummy->next;
}
26. 删除排序数组中的重复项:数组操作
int removeDuplicates(vector& nums) {
if(nums.size() <=1 ) return nums.size();
int index = 1; //[0...index)不包含重复元素
for(int i=1;i
29. 两数相除
除数能减去多少个被除数。
def divide(self, divd: int, dior: int) -> int:
res = 0
sign = 1 if divd ^ dior >= 0 else -1
#print(sign)
divd = abs(divd)
dior = abs(dior)
while divd >= dior:
tmp, i = dior, 1
while divd >= tmp:
divd -= tmp
res += i
i <<= 1
tmp <<= 1
res = res * sign
return min(max(-2**31, res), 2**31-1)
30. 串联所有单词的子串
滑动窗口问题:
由于每个单词长度一样,窗口向右扩展以单词为单位,当遇到不存在的单词,窗口清空,从下一个单词开始匹配
当遇到重复次数过多的单词,窗口左侧收缩到第一次重复的位置的下一个单词,相当于窗口从左侧删除了重复单词
当窗口长度等于单词总长度时,说明遇到了匹配的子串
很慢的解法:每个位置截取一定长度,判断是否符合,遍历的方法
时间复杂度:假设 s 的长度是 n,words 里有 m 个单词,那么时间复杂度就是 O(n * m)。
空间复杂度:两个 HashMap,假设 words 里有 m 个单词,就是 O(m)。
class Solution {
public:
vector findSubstring(string s, vector& words) {
vector ans;
if (words.size() == 0 || s == "")
return ans;
sort(words.begin(), words.end());
unordered_map one;
for(int i=0;i s.length())
return ans;
for (int i = 0; i < s.length()- allwordslen+1; ++i) {
string curs = s.substr(i, allwordslen);
unordered_map two;
for (int j = 0; j < allwordslen; j=j+wordlen){
string cur = curs.substr(j, wordlen);
two[cur]++;
}
if (one == two)
ans.push_back(i);
}
return ans;
}
};
31. 下一个排列
从后向前
void nextPermutation(vector& nums) {
int n = nums.size();
for(int i=n-2;i>=0;i--){
for(int j = n-1;j>i;j--) {
if(nums[j]>nums[i]){
int tmp = nums[j];
nums[j] = nums[i];
nums[i] = tmp;
sort(nums.begin()+i+1,nums.end());
return;
}
}
}
sort(nums.begin(),nums.end());
}
32.最长有效括号
栈
int longestValidParentheses(string s) {
int res = 0;
int start = 0;
stack ss;
for(int i=0;i
33. 搜索旋转排序数组
二分搜索:
class Solution {
public:
int search(vector& nums, int target) {
int left = 0, right = nums.size() - 1;//[l,h]搜索target
while (left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] == target) return mid;
else if(nums[mid] < nums[right]){
if(target>nums[mid] && target <=nums[right]){
left = mid+1;
}else{
right = mid -1;
}
}else{
if(target= nums[left]){
right = mid - 1;
}else{
left = mid+1;
}
}
}
return -1;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置
二分法查找左边界右边界
vector searchRange(vector& nums, int target) {
int l = 0;
int r = nums.size();
vector res = {-1, -1};
// 找左边界
// 还记得 bad version 那个题目吗?我们把 target 看作是 bad version
// 查询左边界就是查找第一个 bad version
while (l < r) {
int mid = l + (r - l) / 2;
if (nums[mid] < target) {
l = mid + 1;
} else {
r = mid;
}
}
if (l == nums.size() || nums[l] != target) return res;
res[0] = l;
// 找右边界,就是查找最后一个 bad version
r = nums.size();
while (l < r) {
int mid = l + (r - l) / 2;
if (nums[mid] > target) {
r = mid;
} else {
l = mid + 1;
}
}
res[1] = l - 1;
return res;
}
35. 搜索插入位置
二分法
41. 缺失的第一个正数
检查 1 是否存在于数组中。如果没有,则已经完成,1 即为答案。
如果 nums = [1],答案即为 2 。
将负数,零,和大于 n 的数替换为 1 。
遍历数组。当读到数字 a 时,替换第 a 个元素的符号。 注意重复元素:只能改变一次符号。由于没有下标 n ,使用下标 0 的元素保存是否存在数字 n。
再次遍历数组。返回第一个正数元素的下标。
如果 nums[0] > 0,则返回 n 。
如果之前的步骤中没有发现 nums 中有正数元素,则返回n + 1。
43. 字符串相乘
模拟数乘运算
class Solution {
public:
string multiply(string num1, string num2) {
if (num1 == "0" || num2 == "0") return "0";
int n1 = num1.length() - 1;
int n2 = num2.length() - 1;
vector mul(n1 + n2 + 2);//编译器默认初始化为0
for(int i=n1;i>=0;i--)
for (int j = n2; j >= 0; j--) {
int bitmul = (num1[i] - '0')*(num2[j] - '0');//两个位上的数的乘积
bitmul += mul[i + j + 1];//先加低位,判断是否有新的进位
mul[i + j] += bitmul / 10;//高位
mul[i + j + 1] = bitmul % 10;//低位
}
//去掉前导0
int i = 0;
while(i < n1 + n2 + 1 && mul[i] == 0)
i++;
string multi;
for (; i < n1 + n2 + 2; i++)
multi.append(to_string(mul[i]));//string类型的append函数
return multi;
}
};
55. 跳跃游戏
这个比较符合动态规划的思想,我们先用动态规划解下这道题。
class Solution {
public boolean canJump(int[] nums) {
if (nums == null) {
return false;
}
boolean[] dp = new boolean[nums.length];
dp[0] = true;
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
// 如果之前的j节点可达,并且从此节点可以到跳到i
if (dp[j] && nums[j] + j >= i) {
dp[i] = true;
break;
}
}
}
return dp[nums.length - 1];
}
}
分析上面的代码,可以看出使用动态规划的时间复杂度是O(n^2),空间复杂度是O(n)。
下面我们使用贪心的思路看下这个问题,我们记录一个的坐标代表当前可达的最后节点,这个坐标初始等于nums.length-1, 然后我们每判断完是否可达,都向前移动这个坐标,直到遍历结束。
如果这个坐标等于0,那么认为可达,否则不可达。
class Solution {
public boolean canJump(int[] nums) {
if (nums == null) {
return false;
}
int lastPosition = nums.length - 1;
for (int i = nums.length - 1; i >= 0; i--) {
// 逐步向前递推
if (nums[i] + i >= lastPosition) {
lastPosition = i;
}
}
return lastPosition == 0;
}
}
这段代码的时间复杂度是O(n),空间复杂度是O(1),可以看出比动态规划解法有了明显的性能提升。
链接:https://leetcode-cn.com/problems/two-sum/solution/dong-tai-gui-hua-yu-tan-xin-suan-fa-jie-jue-ci-wen/
45. 跳跃游戏 II
这题很难,普通动态规划会超时
类似贪心算法的思路,每次尽可能跳的远
int jump(vector& nums) {
//从一个位置跳到它能跳到的最远位置之间的都只需要一步!
//所以,如果一开始都能跳到,后面再跳到的肯定步数要变多!
//dp[i] 到i位置所需的最小步数
if(nums.size() == 1) return 0;
vector dp(nums.size(), 0);
for(int i=0;i=0;j--){//j为跳的长度
if(j+i >= nums.size()-1)
return dp[i] +1;
else if(dp[i+j]==0) //类似dfs,如果前面少步已经可以跳过去了,后面的就不能覆盖
dp[i+j] = dp[i]+1;
else
break;
}
}
return 0;
}
全排列:
class Solution {
public:
vector> res;
vector visited;
vector> permute(vector& nums) {
if(nums.size() == 0) return res;
visited = vector(nums.size(), false);
vector p;
dfs(nums, p);
return res;
}
void dfs(vector& nums, vector& p){
if(p.size() == nums.size()){
res.push_back(p);
return;
}
for(int i=0;i
给定一个可包含重复数字的序列,返回所有不重复的全排列
48. 旋转图像
交换
class Solution {
public:
void rotate(vector>& matrix) {
int n=matrix.size()-1;
int m=matrix.size()/2;
for(int i=0;i<=m;i++)
{
for(int j=i;j
49. 字母异位词分组
对每个字符串排个序
class Solution {
public:
vector> groupAnagrams(vector& strs) {
unordered_map> record;
for(auto s:strs){
string tmp = s;
sort(tmp.begin(), tmp.end());
record[tmp].push_back(s);
}
vector> res;
for(auto one:record){
res.push_back(one.second);
}
return res;
}
};
50,实现 pow(x, n) ,即计算 x 的 n 次幂函数。
class Solution {
public:
double myPow(double x, int n) {
unsigned int _n;
_n = n < 0 ? -(n+1) : n;
double res = 1;
double tmp_pow = x;
while(_n){
if(_n&1){
res *= tmp_pow;
}
_n >>= 1;
tmp_pow *= tmp_pow;
}
return n < 0 ? 1.0 / (res * x) : res;
}
};