整理成了表格形式,里面的网址有的可能不对,后期会完善

This commit is contained in:
ShusenTang
2019-01-01 23:11:07 +08:00
parent 058fcdb4ea
commit c8f833d8e9
173 changed files with 182 additions and 35 deletions
+25
View File
@@ -0,0 +1,25 @@
# [1. Two Sum](https://leetcode.com/problems/two-sum/)
# 思路
刚开始用暴力匹配,后来看了答案恍然大悟,hash会快很多。
# C++
``` C++
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int, int>mp;
vector<int>ans;
int len = nums.size();
for(int i = 0; i < len; i++){
if( mp.find(target - nums[i]) != mp.end()){
ans.push_back(mp[target - nums[i]]);
ans.push_back(i);
return ans;
}
mp[nums[i]] = i;
}
}
};
```
+19
View File
@@ -0,0 +1,19 @@
# [100. Same Tree](https://leetcode.com/problems/same-tree/description/)
# 思路
判断两棵树是否完全相同。最简单的思路就是递归算法,两棵树非空树完全相同的充要条件是:
* 根的值相同;
* 且左子树和右子树都是分别完全相同的。
当然也可以考虑遍历两棵树直到遇到不相同的节点即可返回false,若同时遍历完则返回true。
# C++
``` C++
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if(p == NULL && q==NULL) return true; // 都是空树
if(p == NULL || q == NULL || (p -> val != q -> val)) return false;
return isSameTree(p -> left, q -> left) && isSameTree(p -> right, q -> right);
}
};
```
+58
View File
@@ -0,0 +1,58 @@
# [101. Symmetric Tree](https://leetcode.com/problems/symmetric-tree/description/)
# 思路
## 思路一: 递归
最简单的思路就是递归。若一个非空树是对称树,那么其左右子树是互为镜像的。类似[判断两颗树是否相等的题解](https://github.com/ShusenTang/LeetCode/blob/master/100.%20Same%20Tree.md)
两棵非空树互为镜像的充要条件是:
* 根的值相同;
* 且树一的左子树和树二的右子树互为镜像,树一的右子树和树二的左子树互为镜像。
## 思路二: 非递归
采用类似层次遍历的方法。对于左子树从上往下**从左往右**层次遍历,对于右子树从上往下**从右往左**层次遍历,遍历过程中进行比较。
# C++
## 思路一
``` C++
class Solution {
private:
bool isSymmetricSameTree(TreeNode* p, TreeNode* q) { // 判断两棵树是否互为镜像
if(p == NULL && q==NULL) return true;
if(p == NULL || q == NULL || p -> val != q -> val) return false;
return isSymmetricSameTree(p -> left, q -> right) && isSymmetricSameTree(p -> right, q -> left);
}
public:
bool isSymmetric(TreeNode* root) {
if(root == NULL) return true;
return isSymmetricSameTree(root -> left, root -> right);
}
};
```
## 思路二
``` C++
class Solution {
public:
bool isSymmetric(TreeNode *root) {
if (root == NULL) return true;
TreeNode *left, *right;
queue<TreeNode*> q1, q2;
q1.push(root->left);
q2.push(root->right);
while (!q1.empty() && !q2.empty()){
left = q1.front();
q1.pop();
right = q2.front();
q2.pop();
if (NULL == left && NULL == right)
continue;
if (NULL == left || NULL == right)
return false;
if (left->val != right->val)
return false;
q1.push(left->left);
q1.push(left->right);
q2.push(right->right);
q2.push(right->left);
}
return true;
}
};
```
@@ -0,0 +1,50 @@
# [104. Maximum Depth of Binary Tree](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/)
# 思路
## 思路一: 递归
最简单的思路就是递归。若树非空,则树高就是: 1 + max(左子树高,右子树高),递归出口就是树为空。
## 思路二: 非递归
可考虑用层序遍历的方法计算树高。
用last指针表示每一层的最后一个节点,每当遍历到这个节点即将树高加1并更新last指针。
last初始为root,后面每当遍历完每层最后一个节点后,即将last更新成下一层的最后一个节点,为此需要用一个tmp来不断记录能确定的下一层的最右节点。
# C++
## 思路一
``` C++
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root == NULL) return 0;
else return 1 + max(maxDepth(root -> left), maxDepth(root -> right));
}
};
```
## 思路二
``` C++
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root == NULL) return 0;
int res = 0;
queue<TreeNode *>q;
TreeNode *p, *tmp=NULL, *last=root; // tmp用来更新last用
q.push(root);
while(!q.empty()){
p = q.front();
q.pop();
if(p -> left){
tmp = p -> left;
q.push(tmp);
}
if(p -> right){
tmp = p -> right;
q.push(tmp);
} // tmp记录了直到现在下一层最右的节点
if(p == last){ // 遇到了last
res++; // 树高加1
last = tmp; // 更新last
}
}
return res;
}
};
```
@@ -0,0 +1,46 @@
# [107. Binary Tree Level Order Traversal II](https://leetcode.com/problems/binary-tree-level-order-traversal-ii/description/)
# 思路
题目的意思就是层序遍历的变形,要逆序输出每一层的节点。为此我们先正常层序遍历并用一个stack记录每一层的节点。然后再依次出栈即可。
用last指针指向每一层的最后一个节点,每当遍历到这个节点即说明遍历完一层,此时应该将此层所有节点压入栈。last初始为root,
后面每当遍历完每层最后一个节点后,即将last更新成下一层的最后一个节点,为此需要用一个next_last来不断记录能确定的下一层的最右节点。
时间复杂度和空间复杂度都是O(n)
# C++
``` C++
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
TreeNode *p, *next_last,*last=root;
stack<vector<int>>stk;
queue<TreeNode *>q;
vector<vector<int>>res;
if(root == NULL) return res;
vector<int>tmp; // 存放一层的节点
q.push(root);
while(!q.empty()){
p = q.front();
q.pop();
tmp.push_back(p -> val);
if(p -> left){
q.push(p -> left);
next_last = p -> left;
}
if(p -> right) {
q.push(p -> right);
next_last = p -> right;
}
if(p == last){
stk.push(tmp);
tmp.clear();
last = next_last; // 更新last指针
}
}
while(!stk.empty()){
res.push_back(stk.top());
stk.pop();
}
return res;
}
};
```
@@ -0,0 +1,31 @@
# [108. Convert Sorted Array to Binary Search Tree](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/)
# 思路
题意就是将有序数组转换为平衡搜索树。注意解是不唯一的,例如题中的例子如下解也是可以的:
```
0
/ \
-10 5
\ \
-3 9
```
为了达到平衡,肯定要用二分的思想: 树根肯定是数组中间的那个数,左子树的根是数组左半边中间(`mid=(left+right)/2`)的数(计算mid的时候向上取整和向下取整均可,
题目例子向上取整就是leetcode给的结果,向下取整就是我这里给的结果),右子树的根是数组右半边中间的数。由此可见是一个递归算法。
例如, [-10,-3,0,5,9]中间的数是0所以树根是0, 数组左半边[-10,-3]中间的数是-10,所以左子树的根是-10, 数组右半边[5,9]中间的数是5,所以右子树的根是5...如此递归下去即可。
# C++
``` C++
class Solution {
private:
TreeNode* get_root(vector<int>& nums, int left, int right){
if(left > right) return NULL; // 递归出口
int mid = left + (right - left) / 2; // 不写成mid=(left + right) / 2是为了防止溢出
TreeNode* root = new TreeNode(nums[mid]);
root -> left = get_root(nums, left, mid - 1);
root -> right = get_root(nums, mid + 1, right);
return root;
}
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
return get_root(nums, 0, nums.size() - 1);
}
};
```
@@ -0,0 +1,24 @@
# [11. Container With Most Water](https://leetcode.com/problems/container-with-most-water/)
# 思路
题意就是选择两条线,使其组成的容器装的水最多。水的量可以用宽乘高计算。
由于宽最大就是height两端之间的距离,所以要想在两端之内取得最大值的话只能是高度比较高。
可以考虑设置两个初始分别为两端的指针left和right代表当前容器,不断跳过高度不够高的height使这两个指针往中间靠。这个过程不断循环即可得到结果。
时间复杂度O(n),空间复杂度O(1)
# C++
``` C++
class Solution {
public:
int maxArea(vector<int>& height) {
int res = 0;
int left = 0, right = height.size() - 1;
while(left < right){
int h = min(height[left], height[right]); // 当前容器高度
res = max(res, h * (right - left));
while(height[left] <= h) left++; // 跳过高度不够高的
while(height[right] <= h) right--;
}
return res;
}
};
```
+22
View File
@@ -0,0 +1,22 @@
# [110. Balanced Binary Tree](https://leetcode.com/problems/balanced-binary-tree/description/)
# 思路
一棵非空树是平衡二叉树的充要条件是:
* 其左右子树的高相差不超过1
* 且左右子树都是平衡二叉树。
因此是一个递归算法。
# C++
``` C++
class Solution {
private:
int getDepth(TreeNode *root){
if(root == NULL) return 0;
return 1 + max(getDepth(root -> left), getDepth(root -> right));
}
public:
bool isBalanced(TreeNode* root) {
if(root == NULL) return true;
return (abs(getDepth(root -> left) - getDepth(root -> right)) <= 1) && isBalanced(root -> left) && isBalanced(root -> right);
}
};
```
@@ -0,0 +1,21 @@
# [111. Minimum Depth of Binary Tree](https://leetcode.com/problems/minimum-depth-of-binary-tree/description/)
# 思路
求二叉树的最小深度。依次分为以下情况:
* 若为空树,则返回0
* 否则,若左右子树都为空,返回1
* 否则,若左子树为空,那么最小深度就为右子树的最小深度加1;若右子树为空,那么最小深度就为左子树的最小深度加1;
* 否则(即左右子树都不空),那么最小深度就是min(1+左子树最小深度, 1+右子树最小深度);
# C++
``` C++
class Solution {
public:
int minDepth(TreeNode* root) {
if(root == NULL) return 0;
else if(root -> left == NULL && root -> right == NULL) return 1;
else if(root -> left == NULL) return 1 + minDepth(root -> right);
else if(root -> right == NULL) return 1 + minDepth(root -> left);
return 1 + min(minDepth(root -> left), minDepth(root -> right));
}
};
```
+17
View File
@@ -0,0 +1,17 @@
# [112. Path Sum](https://leetcode.com/problems/path-sum/description/)
# 思路
根据题意,若树空则肯定是false,若非空,则:
* 若没有左右子树(叶子),判断其值是否满足条件;
* 否则,递归判断其左右子树是否满足,只要有一个满足即满足(即或操作)。
# C++
``` C++
class Solution {
public:
bool hasPathSum(TreeNode* root, int sum) {
if(root == NULL) return false;
if(root -> left == NULL && root -> right == NULL && sum == (root -> val)) return true; // 叶子节点
return hasPathSum(root -> left, sum - (root -> val)) || hasPathSum(root -> right, sum - (root -> val));
}
};
```
+22
View File
@@ -0,0 +1,22 @@
# [118. Pascal's Triangle](https://leetcode.com/problems/pascals-triangle/description/)
# 思路
首先明白题目要求返回的是一个vector,其元素也是vector,按照题目规律构造每个vector即可。
# C++
```
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int> > result;
for(int i = 0; i < numRows; i++){
vector<int> tmp;
tmp.push_back(1);
for(int j = 1; j < i; j++){
tmp.push_back(result[i-1][j-1] + result[i-1][j]);
}
if(i > 0) tmp.push_back(1);
result.push_back(tmp);
}
return result;
}
};
```
+26
View File
@@ -0,0 +1,26 @@
# [119. Pascal's Triangle II](https://leetcode.com/problems/pascals-triangle-ii/description/)
# 思路
类似第118题,不过这里要求只输出第rowIndex行,而且尽量使用O(k)的空间。
我们可以依次迭代计算第0、第1...第rowIndex行结果,这是最外层循环,
在得到第i-1行结果时,可以从前往后依次更新数组里的值来得到第i行的结果,这是内层循环。
注意用一个pre来记录第i-1行里面第j个元素的前一个元素的值。
# C++
```
class Solution {
public:
vector<int> getRow(int rowIndex) {
vector<int>result(rowIndex + 1);
result[0] = 1;
for(int i = 1; i <= rowIndex; i++){
int tmp, pre = result[0];
for(int j = 1; j < i; j++){
tmp = result[j];
result[j] += pre;
pre = tmp;
}
result[i] = 1;
}
return result;
}
};
```
+35
View File
@@ -0,0 +1,35 @@
# [12. Integer to Roman](https://leetcode.com/problems/integer-to-roman/)
# 思路
阿拉伯数字转罗马数字。
按照题意将所有可能的罗马数字列出来,再从大到小看能不能将阿拉伯数字转换成罗马数字。详细过程见代码。
# C++
``` C++
class Solution {
public:
string intToRoman(int num) {
map<int, string>int2rom;
string res = "";
int nums[13] = {1,4,5,9,10,40,50,90,100,400,500,900,1000};
int2rom[1] = "I";
int2rom[5] = "V";
int2rom[10] = "X";
int2rom[50] = "L";
int2rom[100] = "C";
int2rom[500] = "D";
int2rom[1000] = "M";
int2rom[4] = "IV";
int2rom[9] = "IX";
int2rom[40] = "XL";
int2rom[90] = "XC";
int2rom[400] = "CD";
int2rom[900] = "CM";
for(int i = 12; i >= 0; i--){
int tmp = num / nums[i];
while(tmp--) res += int2rom[nums[i]];
num %= nums[i];
}
return res;
}
};
```
@@ -0,0 +1,20 @@
# [121. Best Time to Buy and Sell Stock](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/)
# 思路
题目的意思就是求数组prices中,prices[i]-prices[j]的最大值,其中i > j。
因此prices[j]肯定是prices[i]之前的元素中最小的那一个,所以从前往后遍历,并用min_price记录当前位置前的元素中最小的一个。
另外注意单独判断空数组的情况。
# C++
```
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size() == 0) return 0;
int max_profit = 0, min_price = prices[0];
for(int i = 1; i < prices.size(); i++){
if(prices[i] - min_price > max_profit) max_profit = prices[i] - min_price;
if(min_price > prices[i]) min_price = prices[i];
}
return max_profit;
}
};
```
@@ -0,0 +1,42 @@
# [122. Best Time to Buy and Sell Stock II](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/)
# 思路
## 比较好想的思路
如果分别知道了前0到前i天的最大利润dp[0...i],那么前i+1天的最大利润就为:
max(dp[j] + max(prices[i+1] - prices[k])), 其中k属于j~i+1
以上思路时间复杂度为O(n^2), 空间复杂度为O(n)
## 改进
运用贪心的思想:只要有利润(即prices[i] > prices[i-1])就可以买入卖出, 即不错过任何利润。
此时时间复杂度O(n), 空间复杂度为O(1)
# C++
改进前:
```
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size() == 0) return 0;
int dp[prices.size()] = {0};
int min_price;
for(int i = 1; i < prices.size(); i++){
min_price = prices[i];
for(int j = i-1; j >= 0; j--){
if(min_price > prices[j]) min_price = prices[j];
if(dp[j] + prices[i] - min_price > dp[i]) dp[i] = dp[j] + prices[i] - min_price;
}
}
return dp[prices.size() - 1];
}
};
```
改进后:
```
class Solution {
public:
int maxProfit(vector<int>& prices) {
int max_profit = 0;
for(int i = 1; i < prices.size(); i++)
if(prices[i] > prices[i-1]) max_profit += (prices[i] - prices[i-1]);
return max_profit;
}
};
```
+34
View File
@@ -0,0 +1,34 @@
# [125. Valid Palindrome](https://leetcode.com/problems/valid-palindrome/description/)
# 思路
题目要求判断当忽略非字母非数字字符后,给定字符串是否回文(忽略大小写)。
为了方便两个字符是否相等,可以先定义一个transformer函数将字符映射到某个数字:
* 若是0 ~ 9的数字,则映射到数字 -1 ~ -10;
* 若是字母,则映射到数字0 ~ 9
* 若是非字母非数字,全部映射到其他数如999;
然后再用两个指针low和high从两头往中间遍历并比较大小即可。
时间复杂度O(n),空间复杂度O(1)
# C++
```
class Solution {
private:
int transformer(char c){ // 将所有字符映射到整数以方便比较
if('0' <= c && c <= '9') return (-1 * c - 1);
if('a' <= c && c <= 'z') return c - 'a';
if('A' <= c && c <= 'Z') return c - 'A';
return 999;
}
public:
bool isPalindrome(string s) {
int low = 0, high = s.size() - 1;
while(low < high){
while(low < high && transformer(s[low]) == 999) low++; // 跳过非字母非数字
while(low < high && transformer(s[high]) == 999) high--; // 跳过非字母非数字
if(transformer(s[low++]) != transformer(s[high--])) return false;
}
return true;
}
};
```
+27
View File
@@ -0,0 +1,27 @@
# [13. Roman to Integer](https://leetcode.com/problems/roman-to-integer/description/)
# 思路
罗马数字转阿拉伯数字。七个大写字母对应七个数,对应关系可以用一个数组(或者map)来实现。
用res代表结果,将代表罗马数字的字符串s从前往后遍历,如果当前字母对应的数大于等于下一个字母对应的数,则res加上对应的数,否则减。
注意:根据题意是没有可能发生s[i] < s[i + 1] < s[i + 2]的。
# C++
```
class Solution {
public:
int romanToInt(string s) {
vector<int>mp(26);
mp['I' - 'A'] = 1;
mp['V' - 'A'] = 5;
mp['X' - 'A'] = 10;
mp['L' - 'A'] = 50;
mp['C' - 'A'] = 100;
mp['D' - 'A'] = 500;
mp['M' - 'A'] = 1000;
int res = mp[s[s.size() - 1] - 'A'];
for(int i = 0; i < s.size() - 1; i++){
if(mp[s[i] - 'A'] < mp[s[i + 1] - 'A']) res -= mp[s[i] - 'A'];
else res += mp[s[i] - 'A'];
}
return res;
}
};
```
+23
View File
@@ -0,0 +1,23 @@
# [136. Single Number](https://leetcode.com/problems/single-number/description/)
# 思路
## 思路一
最常规的思路就是用一个map或者散列(unordered_map)记录是否出现过。
map: 时间复杂度O(nlogn), 空间复杂度O(n)
unordered_map: 时间复杂度O(n), 空间复杂度O(N)
## 思路二*
若某个数出现两次,则异或操作后得0,所以可以考虑将数组所有元素进行异或操作,最终得到的值就是欲求值。
时间复杂度O(n), 空间复杂度O(1), 完美
# C++
## 思路二
```
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res = 0;
for(int num: nums) res = res ^ num;
return res;
}
};
```
+27
View File
@@ -0,0 +1,27 @@
# [14. Longest Common Prefix](https://leetcode.com/problems/longest-common-prefix/description/)
# 思路
求字符串数组的最长公共前缀。
纵向遍历字符串,即第一次比较所有字符串的第一个字符,第二次比较所有字符串的第二个字符...直到字符串不相等或者超出某个字符串的长度时退出循环。
注意:
append函数可以用来在字符串的末尾追加字符和字符串。由于string重载了运算符,也可以用+=操作实现。
# C++
```
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if(strs.size() == 0) return "";
string res;
int i = 0;
while(1){
for(int j = 0; j < strs.size(); j++){
if(i >= strs[j].size()) return res; // 长度超过最短字符串的长度
if(j == 0 || strs[j][i] == strs[j-1][i]) continue; // 处于第一个字符串或者与上一个字符串的当前位置字符相等
else return res;
}
res += strs[0][i++];
// res.append(1, strs[0][i++]); // 1 代表重复次数
}
}
};
```
+29
View File
@@ -0,0 +1,29 @@
# [141. Linked List Cycle](https://leetcode.com/problems/linked-list-cycle/description/)
# 思路
判断一个链表是否是环。
设置两个指针p1和p2,用步长分别为1和2从前往后遍历,若链表是环则p1和p2总会相遇。
时间复杂度O(n),空间复杂度O(1)
# C++
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head == NULL || head -> next == NULL) return false;
ListNode *p1 = head, *p2 = head -> next;
while(p1 && p2 && p2 -> next){
if(p1 == p2) return true;
p1 = p1 -> next;
p2 = p2 -> next -> next;
}
return false;
}
};
```
+42
View File
@@ -0,0 +1,42 @@
# [15. 3Sum](https://leetcode.com/problems/3sum/)
# 思路
找出数组中的所有和为0的三个数组合。
先想一下如何求所有和为0的两个数的组合。可以这样考虑,先将数组从小到大排序,再设置两个指针low和high分别初始为数组两端,计算两个指针的和sum,
根据sum与0的大小关系适当调整指针:
* 若sum > 0,说明和有点大了,应该小一点,则应该将high左移;
* 若sum < 0,说明和有点小了,应该大一点,则应该将low右移;
* 若sum = 0,说明刚刚好,记录即可,然后同时将low和high向中间移。
三个数的话其实思路是一致的只是外面多一层循环而已。先将数组排序,外层循环就是将当前位置的数定为第一个数,然后就进入内层循环进行类似两个数的和的操作。
注意跳过重复元素。
时间复杂度O(n^2),空间复杂度O(1)
# C++
``` C++
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>>res;
int len = nums.size();
sort(nums.begin(), nums.end());
for(int i = 0; i < len - 2; i++){ // nums[i] 为三个数的第一个数
if(i > 0 && nums[i] == nums[i - 1]) continue;
int low = i + 1, high = nums.size() - 1;
while(low < high){
int sum = nums[i] + nums[low] + nums[high];
if(sum < 0)
while(++low < high && nums[low] == nums[low - 1]) ; // 不断右移low指针
else if(sum > 0)
while(low < --high && nums[high] == nums[high + 1]) ; // 不断左移high指针
else{
res.push_back(vector<int>{nums[i], nums[low], nums[high]});
while(nums[++low] == nums[low - 1]) ;
while(nums[--high] == nums[high + 1]) ;
}
}
}
return res;
}
};
```
+31
View File
@@ -0,0 +1,31 @@
# [155. Min Stack](https://leetcode.com/problems/min-stack/description/)
# 思路
实现最小栈,要求所有操作的时间复杂度都为O(1)。
可考虑用两个站S1、S2,S1就正常记录minStack的值,而S2的栈顶记录了当前Minstack的最小值。S2的更新规则见代码。
# C++
```
class MinStack {
private:
stack<int> s1;
stack<int> s2; // S2的栈顶记录了当前Minstack的最小值
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
s1.push(x);
if (s2.empty() || x <= getMin()) s2.push(x); // 新来的x比getMin还小,说明是新的最小值,应该入栈S2
}
void pop() {
if (s1.top() == getMin()) s2.pop(); // 若pop掉了最小元素,则S2也应该pop一个
s1.pop();
}
int top() {
return s1.top();
}
int getMin() { // S2的栈顶记录了当前Minstack的最小值
return s2.top();
}
};
```
+33
View File
@@ -0,0 +1,33 @@
# [16. 3Sum Closest](https://leetcode.com/problems/3sum-closest/)
# 思路
类似[15. 3Sum](https://leetcode.com/problems/3sum/),先对数组进行排序,外层循环就是遍历一遍数组,内层循环就是用两个指针low和high,与3sum不同的就是
每次循环要判断记录当前sum和target的差值。
时间复杂度O(n^2)
# C++
``` C++
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int len = nums.size();
long long res=nums[0] + nums[1] + nums[2], min_gap, sum, curr_gap; // 防止溢出所以用long long型
min_gap = abs(res - target);
sort(nums.begin(), nums.end());
for(int i = 0; i < len - 2; i++){
int low = i + 1, high = len - 1;
while(low < high){
sum = nums[i] + nums[low] + nums[high];
curr_gap = abs(sum - target);
if(curr_gap < min_gap){
res = sum;
min_gap = curr_gap;
}
if(target < sum) high--;
else if(target > sum) low++;
else return target;
} // low == high
}
return res;
}
};
```
@@ -0,0 +1,52 @@
# [160. Intersection of Two Linked Lists](https://leetcode.com/problems/intersection-of-two-linked-lists/description/)
# 思路
题目要求求两个链表的交集,需要注意的事实是只要两个链表有一个节点重合了,那么其后的节点也是重合的,这个节点也即所求,就如题图c1所示。
先计算两个链表的长度差delta_len,然后设置两个工作指针p1和p2,保证p1所在的链表是较短的链表, 再让p2先往后移动delta_len步,最后p1、p2同时往后移动
直到p1 == p2即到达所求节点c1或者遇到NULL。
时间复杂度O(n), 空间复杂度O(1)
# C++
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(!(headA && headB)) return NULL;
int delta_len = 0;
ListNode *p1 = headA, *p2 = headB, *tmp = NULL;
while(p1 && p2){
p1 = p1 -> next;
p2 = p2 -> next;
}
if(p1) tmp = p1;
else tmp = p2;
while(tmp){
delta_len++;
tmp = tmp -> next;
}
//以上代码求链表长度差
if(p1){ // 保证p1长度不大于p2
p1 = headB;
p2 = headA;
}
else{
p1 = headA;
p2 = headB;
}
while(delta_len--) p2 = p2 -> next;
while(p1 && p1 != p2){
p1 = p1 -> next;
p2 = p2 -> next;
}
return p1;
}
};
```
@@ -0,0 +1,46 @@
# [167. Two Sum II - Input array is sorted](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/)
# 思路
## 思路一
先遍历一遍numbers用一个map记录每个元素的下标(从1开始),然后遍历一遍查看mp[target - numbers[i]]是否不为0,是则找到。
仔细观察可知两个循环可以合并在一起,但是要注意是先判断再用map记录,否则假如target刚好是某个元素的两倍的话就会返回两个同样的下标。
map查找的复杂度为O(logn),所以总的时间复杂度O(nlogn), 空间复杂度O(n)
## 思路二
思路一没有运用到数组已经排序的这一信息,复杂度较高。
同时从首尾向中间遍历,若元素和大于target,则尾指针前移;若元素和小于target,首指针前移。
此时时间复杂度O(n), 空间复杂度O(1).
# C++
## 思路一
```
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
map<int, int>mp;
vector<int>result;
for(int i = 0; i < numbers.size(); i++) {
if(mp[target - numbers[i]] != 0){
result.push_back(mp[target - numbers[i]]);
result.push_back(i + 1);
return result;
}
mp[numbers[i]] = i + 1;
}
}
};
```
## 思路二
```
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int low = 0, high = numbers.size() - 1;
while(1){
if(numbers[low] + numbers[high] == target)
return vector<int>({low+1, high+1});
else if(numbers[low] + numbers[high] < target)
low++;
else
high--;
}
}
};
```
@@ -0,0 +1,18 @@
# [168. Excel Sheet Column Title](https://leetcode.com/problems/excel-sheet-column-title/description/)
# 思路
题目的要求相当于是十进制转二十六进制。用一个循环每次对n取模然后n除26进入下一次循环即可。
不过需要注意的是,题目给的是1-26对应A-Z而不是0-25对应A-Z,所以每次循环时都要对n作自减操作。
# C++
```
class Solution {
public:
string convertToTitle(int n) {
string res;
while (n > 0) {
res = (char)('A' + (--n) % 26) + res;
n /= 26;
}
return res;
}
};
```
+61
View File
@@ -0,0 +1,61 @@
# [169. Majority Element](https://leetcode.com/problems/majority-element/description/)
# 思路
题目要求就是求数组主元素,主元素就是在数组中出现次数超过元素个数一半的元素,题目保证主元素一定存在。
## 思路一: 排序
若对数组nums进行排序,则nums[n/2]就是主元素。
时间复杂度为O(nlogn)。
## 思路二: 投票算法
因为主元素总是存在。所以每出现两个不一样的数就可以忽视这两个数。最终剩下的就是主元素。
我们可以从前往后遍历,如果某数和当前major相同那么count++,否则count--,如果count为零了,那么当前major应该改成当前这个数。
时间复杂度O(n)。
## 思路三: 位运算
如果将每个数都转换为二进制的话,那么对于每一位上就只能是0或1。对每一位,取出现次数较多的数(0或1),这样组成的数就是主元素。
时间复杂度O(n)。
# C++
## 思路一
```
// 提交结果为16ms
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end());
return nums[nums.size() / 2];
}
};
```
## 思路二
```
// 提交结果为12ms,较思路一有提升
class Solution {
public:
int majorityElement(vector<int>& nums) {
int major = nums[0], count = 0;
for(int num : nums){ // 范围for语句
if(major == num) count++;
else if(count == 1) major = num;
else count--;
}
return major;
}
};
```
## 思路三
```
// 提交结果20ms
class Solution {
public:
int majorityElement(vector<int>& nums) {
vector<int>bit(32);
for (int num: nums){
for (int i = 0; i < 32; i++)
if(num & (1 << i)) bit[i]++;
}
int major=0;
for (int i = 0; i < 32; i++) {
if(bit[i] = bit[i] > nums.size() / 2) major += bit[i] * (int)pow(2, i);
}
return major;
}
};
```
@@ -0,0 +1,57 @@
# [17. Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/)
# 思路
## 思路一
举例说明吧。
1. `digits = "2"`时,结果显然是`res = ["a","b","c"]`
2. `digits = "23"`时,1中res的每一个字符串后都可以接d、e、f任意一个,所以可以先将1中的res中所有元素复制两遍
变成["a","b","c","a","b","c","a","b","c"],再在此时res的每一个元素后面合适地接上d、e、f其中一个就变成了
["ad","bd","cd","ae","be","ce","af","bf","cf"]。
由此就可以写出代码了。
时间复杂度O(n^2),空间复杂度O(1)
## 思路二
其实可以将此题看成求解一棵树的所有root(root可以看做是空)到叶子的路径。
例如当`digits = "23"`时,树应该是这个样子:
```
root
/ | \
2: a b c
/|\ /|\ /|\
3: def def def
```
所以就可以用DFS求解这题了。
# C++
## 思路一
```C++
class Solution {
public:
vector<string> letterCombinations(string digits) {
int len = digits.size();
vector<string>res;
if(len == 0) return res;
const vector<string>digit2char{"","","abc","def","ghi",
"jkl","mno","pqrs","tuv","wxyz"};
res.push_back("");
for(int i = 0; i < len; i++){
int digit = int(digits[i] - '0');
int curr_res_size = res.size();
for(int k = 0; k < digit2char[digit].size() - 1; k++) // 将res中所有元素复制几遍
for(int j = 0; j < curr_res_size; j++)
res.push_back(res[j]);
for(int k = 0; k < digit2char[digit].size(); k++)
for(int j = 0; j < curr_res_size; j++)
res[k * curr_res_size + j] += digit2char[digit][k];
}
return res;
}
};
```
## 思路二
见[此处](https://leetcode.com/problems/letter-combinations-of-a-phone-number/discuss/8454/My-C%2B%2B-solution-use-DFS).
有时间了再自己实现一下。
@@ -0,0 +1,18 @@
# [171. Excel Sheet Column Number](https://leetcode.com/problems/excel-sheet-column-number/description/)
# 思路
第[168题](https://leetcode.com/problems/excel-sheet-column-title/description/)是将10进制转换为26进制,这题是将26进制转换为10进制.
例:将k进制数"abcd"转换为10进制数:`res = d * k^0 + c * k^1 + b * k^2 + a * k^3`.
# C++
```
class Solution {
public:
int titleToNumber(string s) {
int res = 0, multiplier = 1;
for(int i = s.size() - 1; i >= 0; i--){
res += (int)(s[i] - 'A' + 1) * multiplier;
multiplier *= 26;
}
return res;
}
};
```
@@ -0,0 +1,29 @@
# [172. Factorial Trailing Zeroes](https://leetcode.com/problems/factorial-trailing-zeroes/description/)
# 思路
求某个数的阶乘有多少个0.
我们知道要想产生10只能是因子2与5相乘。要想产生因子2比较简单,只要是偶数就行;而产生因子5只能是5的倍数例如5、10、15、20...由此可知我们不用考虑怎么产生2
(因为2实在是太多了,每个一个数就有一个,而5则需要每隔4个数才有一个)。
所以题目转换成1-n这n个数因式分解后有多少个5。很明显5的倍数至少有一个5的因子,但是我们需要注意到25、50等等其实是蕴含了两个5的因子,125、250等蕴含了三个5的因子...
例子:
n = 4617.
5^1 : 4617 ÷ 5 = 923.4, 所以一共得到923个因子5
5^2 : 4617 ÷ 25 = 184.68, 所以又得到额外的184个因子5;
5^3 : 4617 ÷ 125 = 36.936, 所以又得到额外的36个因子5;
5^4 : 4617 ÷ 625 = 7.3872, 所以又得到额外的7个因子5;
5^5 : 4617 ÷ 3125 = 1.47744, 所以又得到额外的1个因子5;
5^6 : 4617 ÷ 15625 = 0.295488, 结果小于1,停止循环。
所以 4617! 有 923 + 184 + 36 + 7 + 1 = 1151 个尾0.
[参考](https://leetcode.com/problems/factorial-trailing-zeroes/discuss/52373/Simple-CC++-Solution-(with-detailed-explaination))
# C++
```
class Solution {
public:
int trailingZeroes(int n) {
int result = 0;
for(long long i=5; n / i > 0; i *= 5){
result += (n/i);
}
return result;
}
};
```
+46
View File
@@ -0,0 +1,46 @@
# [18. 4Sum](https://leetcode.com/problems/4sum/)
# 思路
和[3sum](https://github.com/ShusenTang/LeetCode/blob/master/15.%203Sum.md)这题基本一样的,注意这题由于循环层数比较多所以如果能
在外层循环判断一下的话可以提前终止循环或跳过某次循环,见代码。
时间复杂度O(n^3)
# C++
``` C++
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>>res;
int len = nums.size(), low, high, sum;
if(len < 4) return res;
sort(nums.begin(), nums.end());
for(int i = 0; i < len - 3; i++){
if(i != 0 && nums[i] == nums[i - 1]) continue;
// 提前退出或跳过不可能的情况
if(nums[i]+nums[i+1]+nums[i+2]+nums[i+3] > target) break; // 最小的都比target大了,可以提前终止循环
if(nums[i]+nums[len-3]+nums[len-2]+nums[len-1] < target) continue;
for(int j = i + 1; j < len - 2; j++){
if(j != i + 1 && nums[j] == nums[j - 1]) continue;
low = j + 1;
high = len - 1;
while(low < high){
sum = nums[i] + nums[j] + nums[low] + nums[high];
if(sum < target)
while(++low < high && nums[low] == nums[low - 1]) ; // 不断右移low指针
else if(sum > target)
while(low < --high && nums[high] == nums[high + 1]) ; // 不断左移high指针
else{
vector<int>tmp = {nums[i], nums[j], nums[low++], nums[high--]};
res.push_back(tmp);
while(low < high && nums[low] == nums[low - 1]) low++;
while(low < high && nums[high] == nums[high + 1]) high--;
}
}
}
}
return res;
}
};
```
+21
View File
@@ -0,0 +1,21 @@
# [189. Rotate Array](https://leetcode.com/problems/rotate-array/description/)
# 思路
题意就是循环右移k步,如果仔细观察结果可知,结果相当于先对数组整体进行翻转,再对前k个元素和后面的元素分别进行翻转的结果。
例如[1,2,3,4,5,6,7], k = 3:
先整体翻转:[7,6,5,4,3,2,1];
再对前3个元素翻转:[5,6,7,4,3,2,1];
再对后面的元素翻转:【5,6,7,1,2,3,4]。
时间复杂度O(n), 空间复杂度O(1)。
# C++
```
class Solution {
public:
void rotate(vector<int>& nums, int k) {
k %= nums.size(); // 注意先取模
if(k == 0) return;
reverse(nums.begin(), nums.end());
reverse(nums.begin(), nums.begin() + k);
reverse(nums.begin() + k, nums.end());
}
};
```
@@ -0,0 +1,40 @@
# [19. Remove Nth Node From End of List](https://leetcode.com/problems/remove-nth-node-from-end-of-list/)
# 思路
题意就是去掉链表中倒数第n个节点。
涉及到链表中倒数节点的思路都是使用两个指针p1、p2,两个指针初始都是head,p2先走n步,然后p1、p2再同时走直到p2到达链尾,
此时p1就位于链表倒数第n个节点的前一个节点。
需要注意当要删除的节点就是head时需要特殊处理。
时间复杂度O(N),空间复杂度O(1)
# C++
``` C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(n <= 0) return head;
ListNode *p1 = head, *p2 = head;
while(n--) p2 = p2 -> next; // p2先走n步
if(p2 == NULL){ // 此时删掉的应该是head
head = head -> next;
delete p1;
return head;
}
while(p2 -> next){
p1 = p1 -> next;
p2 = p2 -> next;
} // 此时p1位于倒数第n个节点的前一个节点
p2 = p1 -> next;
p1 -> next = p2 -> next;
delete p2;
return head;
}
};
```
+48
View File
@@ -0,0 +1,48 @@
# [190. Reverse Bits](https://leetcode.com/problems/reverse-bits/description/)
# 思路
将给定的数的二进制进行翻转。
## 思路一、常规思路
定义res为32位无符号型且初始值为0,可从低位到高位依次取得给定的数的32位bit值,再用或运算赋值给res的最低位,res不断左移。
可将给定的数与一个mask进行与操作取得特定位数上的值,mask只有一位是1。可通过对mask移位(如下代码)或者对给定的数移位来达到取特定位的目的。
## 思路二、巧妙思路
* 1、若只有两位: ab, 则将第一位右移一位、将第二位左移一位即可得到ba;
* 2、若只有四位: abcd, 则先将前两位右移两位、后两位左移两位,再分别对两个两位进行情况1的操作即可得到dcba;
* 3、若只有八位: abcdefgh, 则先将前四位右移四位、后四位左移四位,再依次进行情况1、2的操作即可得到hgfedcba;
* ......
则针对此题的具体步骤是:
* 将前16位右移16位,将后16位左移16位;
* 将0-7、16-23位右移八位,将8-15、24-31位左移八位(最低位为31位);
* ......
# C++
## 思路一
```
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
uint32_t res = 0;
uint32_t mask = 1;
for (uint32_t i = 0; i < 32; ++i) {
// n & mask取得第i位
res = (res << 1) | ((n & mask) >> i);
mask = mask << 1;
}
return res;
}
};
```
## 思路二
```
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
n = (n >> 16) | (n << 16);
n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8);
n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4);
n = ((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2);
n = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1);
return n;
}
};
```
+52
View File
@@ -0,0 +1,52 @@
# [191. Number of 1 Bits](https://leetcode.com/problems/number-of-1-bits/description/)
# 思路
## 思路一
不断循环,每次循环得到最低位的值,最后可得到所有位1的个数。怎样得到最低位的值,两种方法:
* 1、若num为奇数,则num的最低位肯定为1;
* 2、用一个只有低位是1的mask与num进行与操作即可得到最低位;
## 思路二*
依然是循环,但是每次循环不是得到最低位的值,而是每次循环去掉一个1,用一个count计数即可得到答案。
# C++
## 思路一
```
// 方法1
class Solution {
public:
int hammingWeight(uint32_t n) {
int res = 0;
while(n != 0){
res += (n % 2);
n /= 2;
}
return res;
}
};
// 方法2
class Solution {
public:
int hammingWeight(uint32_t n) {
int res = 0;
uint32_t mask = 1;
for(int i = 0; i < 32; i++){
res += (n & mask);
n = n >> 1;
}
return res;
}
};
```
## 思路二*
```
class Solution {
public:
int hammingWeight(uint32_t n) {
int count = 0;
while (n) {
n &= (n - 1); // 去掉最后的1
count++;
}
return count;
}
};
```
+59
View File
@@ -0,0 +1,59 @@
# [198. House Robber](https://leetcode.com/problems/house-robber/description/)
# 思路
简单动态规划。
设直到第i个街道小偷能获得最大的收益为dp[i], 有两种情况:
* 若不偷这个街区,则dp[i] = dp[i-1]
* 若偷这个街区,则dp[i] = dp[i-2] + nums[i]。
`dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])`.
时间复杂度和空间复杂度都为O(n)
## 空间优化
注意到每次更新dp[i]时只会用到到nums中的nums[i]而不会用到之前的, 所以完全可以吧nums作为dp,这样空间复杂度就为O(1),但是修改了原数组nums.
## 空间优化且不改变原数组
用pre记录dp[i-1],这样既不改变原数组nums也使得空间复杂度为o(1), 完美
# C++
```
class Solution {
public:
int rob(vector<int>& nums){
if(nums.empty()) return 0;
if(nums.size() == 1) return nums[0];
vector<int>dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for(int i = 2; i < nums.size(); i++) dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
return dp[nums.size() - 1];
}
};
```
## 空间优化
```
class Solution {
public:
int rob(vector<int>& nums){
if(nums.empty()) return 0;
if(nums.size() == 1) return nums[0];
nums[1] = max(nums[0], nums[1]);
for(int i = 2; i < nums.size(); i++) nums[i] = max(nums[i - 1], nums[i - 2] + nums[i]);
return nums[nums.size() - 1];
}
};
```
## 空间优化且不修改原数组
```
class Solution {
public:
int rob(vector<int>& nums){
if(nums.empty()) return 0;
if(nums.size() == 1) return nums[0];
int tmp, res, pre = nums[0];
res = max(nums[0], nums[1]);
for(int i = 2; i < nums.size(); i++) {
tmp = res;
res = max(res, pre + nums[i]);
pre = tmp;
}
return res;
}
};
```
+44
View File
@@ -0,0 +1,44 @@
# [2. Add Two Numbers](https://leetcode.com/problems/add-two-numbers/)
# 思路
从前到尾不断将两个链表的元素相加就行了,这个过程中一直会用到一个代表进位的变量cin,另外注意处理两个链表不等长的情况。
# C++
``` C++
class Solution {
private:
int digit_add(const int a, const int b, int& cin){ // 先定义一个一位数字相加的函数,方便后面使用,注意是用引用的方式传入cin的
int sum = a + b + cin;
cin = sum / 10;
return sum % 10;
}
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int cin = 0; // 进位
ListNode *root = new ListNode(digit_add(l1 -> val, l2 -> val, cin)); // root是最后要返回的指针
ListNode *last = root; // last指向当前结果链表中的最后一个元素
l1 = l1 -> next;
l2 = l2 -> next;
while(l1 && l2){ // 由前先后对两个链表的元素相加
last -> next = new ListNode(digit_add(l1 -> val, l2 -> val, cin));
last = last -> next;
l1 = l1 -> next;
l2 = l2 -> next;
}
if(l2) l1 = l2; // 若l2长一些,将l1指向l2,后续就只对l1处理就行了.
while(l1 && cin > 0){
last -> next = new ListNode(digit_add(l1 -> val, 0, cin));
last = last -> next;
l1 = l1 -> next;
}
if(l1) last -> next = l1; // cin = 0,直接将l1后续的所有节点接到last后面就行了
else{ // l1 == NULL cin >= 0
if(cin > 0){
last -> next = new ListNode(cin);
last = last -> next;
}
last -> next = NULL;
}
return root;
}
};
```
+30
View File
@@ -0,0 +1,30 @@
# [20. Valid Parentheses](https://leetcode.com/problems/valid-parentheses/description/)
# 思路
用一个栈来存放左括号,每次遇到左括号就将其入栈,遇到右括号就查看是否与栈顶元素配对,若能配对则pop栈顶元素,继续下一循环,否则返回false。
退出循环后,若栈不空,说明还剩下未配对的左括号,则应该返回false。
时间复杂度O(n), 空间复杂度O(n)
# C++
```
class Solution {
private:
bool isLegal(const char& a, const char&b){
if(a == '(' && b == ')') return true;
if(a == '[' && b == ']') return true;
if(a == '{' && b == '}') return true;
return false;
}
public:
bool isValid(string s) {
stack<char>stk;
for(int i = 0; i < s.size(); i++){
if(s[i] == ')' || s[i] == '}' || s[i] == ']'){
if(stk.empty() || !isLegal(stk.top(), s[i])) return false;
stk.pop();
}
else stk.push(s[i]);
}
if(!stk.empty()) return false;
return true;
}
};
```
+28
View File
@@ -0,0 +1,28 @@
# [202. Happy Number](https://leetcode.com/problems/happy-number/description/)
# 思路
按照题目的意思进行循环,判断每次循环结果是否为1,若是则返回true,否则用map记录结果,然后修改n进行下一次循环。
# C++
```
class Solution {
private:
int count_sum(int n){ // 定义题目中所述的求和函数
int sum = 0;
while(n > 0){
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
public:
bool isHappy(int n) {
map<int, int>mp;
while(n){
if(n == 1) return true;
if(mp[n] == 1) return false;
mp[n] = 1;
n = count_sum(n);
}
return false;
}
};
```
@@ -0,0 +1,40 @@
# [203. Remove Linked List Elements](https://leetcode.com/problems/remove-linked-list-elements/description/)
# 思路
删除链表中值满足条件的节点,常规操作。
设两个指针pre和p,p为工作指针,pre为p的前一个节点,判断p的值是否为val:
* 若是,将pre的next指向p的next,再删除节点p;
* 若不是,将pre指向p即可。
最后将p指向pre的next。
为了操作方便,可以设置一个头结点。
# C++
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *list_head = new ListNode(0); // 设一个头结点便于操作
list_head -> next = head;
ListNode *pre = list_head, *p = head, *target;
while(p){
if(p -> val == val){
target = p;
pre -> next = p -> next;
target -> next = NULL;
delete target;
}
else pre = p;
p = pre -> next;
}
return list_head -> next;
}
};
```
+50
View File
@@ -0,0 +1,50 @@
# [204. Count Primes](https://leetcode.com/problems/count-primes/description/)
# 思路
## 思路一
不断循环,判断某个数是否是素数,判断思路:
对于大于1的整数n,若n能被2、3...sqrt(n)中任意一个数整除,则n不是素数,否则是素数。
时间复杂度O(n^(3/2)), 空间复杂度O(1)
## 思路二*(厄拉多塞筛法)
求解有多少个小于某个数的素数的快速方法--厄拉多塞筛法([参考博客](https://blog.csdn.net/lisonglisonglisong/article/details/45309651))
西元前250年,希腊数学家厄拉多塞(Eeatosthese)想到了一个非常美妙的质数筛法,减少了逐一检查每个数的的步骤,可以比较简单的从一大堆数字之中,筛选出质数来,这方法被称作厄拉多塞筛法(Sieve of Eeatosthese)。
具体操作:先将 2~n 的各个数放入表中,然后在2的上面画一个圆圈,然后划去2的其他倍数;第一个既未画圈又没有被划去的数是3,将它画圈,再划去3的其他倍数;现在既未画圈又没有被划去的第一个数 是5,将它画圈,并划去5的其他倍数……依次类推,一直到所有小于或等于 n 的各数都画了圈或划去为止。这时,表中画了圈的以及未划去的那些数正好就是小于 n 的素数。
时间复杂度O(n),空间复杂度O(n)
# C++
## 思路一
```
class Solution {
private:
bool isPrime(int n){
if(n < 2) return false;
for(int i = 2; i <= sqrt(n); i++)
if(n % i == 0) return false;
return true;
}
public:
int countPrimes(int n) {
int count = 0;
for(int i = 2; i < n; i++)
if(isPrime(i)) count++;
return count;
}
};
```
## 思路二
```
class Solution {
public:
int countPrimes(int n) {
vector<unsigned int>nums(n, 1); // 0代表被划去,1代表没被划去
int count = 0;
for(int i = 2; i < n; i++){
if(nums[i] == 0) continue;
count++;
for(int j = 2; j * i < n; j++) nums[j * i] = 0;
}
return count;
}
};
```
+27
View File
@@ -0,0 +1,27 @@
# [205. Isomorphic Strings](https://leetcode.com/problems/isomorphic-strings/description/)
# 思路
题意就是判断两个字符串是否是同形的,重点就是理解对这个同形的意思。
若把字符串s中的某几种字符用另外某几种字符全部代替(只能是一一对应,即全部的字符x都要变成同一个字符y,x可以等于y)就变成字符串t,则s和t就是同形的。
由此可见,若将s和t中个每个字符都用一个数代替, 这个数代表了该字符是第几个出现的(如paper -> 12134, title -> 12134), 则结果应该是一样的。
为了记录是否出现过,用map来实现,此外还用一个count计数。
时间复杂度O(nlogn)
# C++
```
class Solution {
public:
bool isIsomorphic(string s, string t) {
map<char, int>mp1, mp2;
int count = 1;
for(int i = 0; i < s.size(); i++){
if(mp1[s[i]] != mp2[t[i]]) return false; // 判断当前字符转变后是否相等
if(mp1[s[i]] == 0){
mp1[s[i]] = count;
mp2[t[i]] = count++;
}
}
return true;
}
};
```
+63
View File
@@ -0,0 +1,63 @@
# [206. Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/description/)
# 思路
## 思路一:迭代
先设置一个头结点List_head,其next指向NULL。然后从待翻转链表中一次取一个节点p出来,将p的next指向List_head的nextList_head的next指向p。
循环上述操作直到p为NULL。
## 思路二:递归
也可以采用递归的方式:
* 递归出口:若head == NULL 或者 head -> next == NULL,直接返回head即可;
* 递归主体:用q记录head的下一个节点,然后令p等于reverseList(q),则p的最后一个非空节点就是q,将q的next令为headhead的next令为NULL,再返回p即可。
# C++
## 思路一
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *List_head = new ListNode(0);
List_head -> next = NULL;
ListNode *p = head, *tmp;
while(p){
tmp = p -> next;
p -> next = List_head -> next;
List_head -> next = p;
p = tmp;
}
return List_head -> next;
}
};
```
## 思路二
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head == NULL || head -> next == NULL) return head;
ListNode *p, *q;
q = head -> next;
p = reverseList(q);
q -> next = head;
head -> next = NULL;
return p;
}
};
```
+44
View File
@@ -0,0 +1,44 @@
# [21. Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/description/)
# 思路
合并两个已有序的链表,注意题目给的链表没有头结点,所以为了操作方便可以自己设一个头结点,最后返回头结点的下一个节点即可。两个链表的工作指针就用传进来的l1和
l2即可。
# C++
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *res = new ListNode(0); // 自己设一个头结点
ListNode *pre = res; // pre代表已排好序的链表的最后一个节点
while(l1 && l2){
if(l1 -> val <= l2 -> val){
pre -> next = l1;
l1 = l1 -> next;
}
else{
pre -> next = l2;
l2 = l2 -> next;
}
pre = pre -> next; // pre后移
}
// 跳出循环时,l1和l2其中一个是NULL
if(l1) pre -> next = l1;
else pre -> next = l2;
// 释放头结点
ListNode *head = res;
res = head -> next;
head -> next = NULL;
delete head;
return res;
}
};
```
+15
View File
@@ -0,0 +1,15 @@
# [217. Contains Duplicate](https://leetcode.com/problems/contains-duplicate/description/)
# 思路
先对数组进行排序,排序后重复的数一定是相邻的,再遍历一遍即可。
# C++
```
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
sort(nums.begin(), nums.end());
for(int i = 1; i < nums.size(); i++)
if(nums[i] == nums[i-1]) return true;
return false;
}
};
```
+31
View File
@@ -0,0 +1,31 @@
# [219. Contains Duplicate II](https://leetcode.com/problems/contains-duplicate-ii/description/)
# 思路
判断数组是否有重复元素,而且重复元素下标差的绝对值不大于k,首先简单的思路就是用map,这里给出另一种解法:
定义一个结构体num_with_index记录数组元素的值和下标,然后再对结构体按照值进行排序,排序后重复元素肯定相邻,再判断下标是否满足条件即可。
# C++
```
class Solution {
struct num_with_index{
int num;
int index;
};
public:
// 这里必须加static否则编译不过,原因参考https://www.cnblogs.com/scoyer/p/6533685.html
static bool mycompare(const num_with_index &a, const num_with_index &b){
return a.num > b.num;
}
bool containsNearbyDuplicate(vector<int>& nums, int k) {
vector<num_with_index>newnums;
num_with_index newnum;
for(int i = 0; i < nums.size(); i++){
newnum.num = nums[i];
newnum.index = i;
newnums.push_back(newnum);
}
sort(newnums.begin(), newnums.end(), mycompare);
for(int i = 1; i < nums.size(); i++)
if(newnums[i].num == newnums[i-1].num && abs(newnums[i].index - newnums[i-1].index) <= k) return true;
return false;
}
};
```
+67
View File
@@ -0,0 +1,67 @@
# [22. Generate Parentheses](https://leetcode.com/problems/generate-parentheses/)
# 思路
给定括号的个数,返回所有可能的合法的括号组成情况。
## 思路一
用str代表一个合法的括号串,初始为空。再作如下定义:
* 用left代表当前还能往str加入左括号的个数,left初始为n;
* 用right代表当前还能往str加入右括号的个数,right初始为0(即str不能以右括号开头)。
定义一个递归函数helper:
* 递归出口:当left和right都等于0,此时str就应该是一个合法的括号串,push进结果数组中再返回即可。
* 否则,若`right > 0`, 说明此时可以添加右括号了,right应该减一,进入递归,跳出递归后,若`left > 0`,则添加左括号,left减一right加一。
## 思路二
其实和思路一类似,不过亲测要比思路一快一些。
用str代表一个括号字符串,初始为空。再作如下定义:
* left代表使str成为含有n个括号的合法串还应该向str中加入的左括号的个数(即若当前str中左括号的个数为k,则left=n-k),left初始为n
* right定义同理,right初始也为n。(注意和思路一不一样)
有了以上定义,那么当`left > right`时,说明str中左括号数小于右括号数,例如"())",则str不合法。
定义一个递归函数helper:
* 递归出口:`left > right`时直接return,或者当left和right都等于0,此时str就应该是一个合法的括号串,push进结果数组中再返回即可。
* 否则,若`right > 0`, 说明此时可以添加右括号了,right应该减一,进入递归,跳出递归后,若`left > 0`,则添加左括号,left减一(与思路一不同:right不加一)。
## 思路一
``` C++
class Solution {
private:
void helper(vector<string> &res, string str, int left, int right){
// left代表此时还能添加的左括号的个数,right代表此时能添加上的右括号的个数
if(left == 0 && right == 0){
res.push_back(str);
return;
}
if(right > 0) helper(res, str + ")", left, right - 1);
if(left > 0) helper(res, str + "(", left - 1, right + 1); // 添加一个左括号后就可以多添加一个右括号了
}
public:
vector<string> generateParenthesis(int n) {
vector<string>res;
helper(res, "", n, 0); // 不能一开始就添加右括号,所以right=0
return res;
}
};
```
## 思路二
与思路一不同的只有三个地方,下面标了出来
``` C++
class Solution {
private:
void helper(vector<string> &res, string str, int left, int right){
// left代表使str成为含有n个括号的合法串还应该向str中加入的左括号的个数,right同理
if(left > right) return; // 不同点1
if(left == 0 && right == 0){
res.push_back(str);
return;
}
if(right > 0) helper(res, str + ")", left, right - 1);
if(left > 0) helper(res, str + "(", left - 1, right); // 不同点2
}
public:
vector<string> generateParenthesis(int n) {
vector<string>res;
helper(res, "", n, n); // 不同点3
return res;
}
};
```
@@ -0,0 +1,45 @@
# [225. Implement Stack using Queues](https://leetcode.com/problems/implement-stack-using-queues/description/)
# 思路
用队列实现栈。
栈是先进后出,队列是后进先出,所以要想用队列实现栈时为了返回栈顶元素就得完整pop一遍队列里的元素,或者在进队的时候就将栈顶元素移到队头,下面的代码采用后者思路。
因为pop和top都要求找到栈顶元素,采用前者思路的话会产生混乱。
注意学习stl中queue的一些操作:push、pop、front
# C++
```
class MyStack {
private:
queue<int>q;
public:
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) { // 先将x进队,然后再将x前面的元素依次pop出来并入队尾,这样x就位于队头了。
q.push(x);
int tmp;
for(int i = 0; i < q.size() - 1; i++){
tmp = q.front();
q.pop();
q.push(tmp);
}
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int res = q.front();
q.pop();
return res;
}
/** Get the top element. */
int top() {
return q.front();
}
/** Returns whether the stack is empty. */
bool empty() {
return q.empty();
}
};
```
+21
View File
@@ -0,0 +1,21 @@
# [226. Invert Binary Tree](https://leetcode.com/problems/invert-binary-tree/description/)
# 思路
翻转二叉树。
递归算法的话很简单:
* 若为空树则返回空即可。
* 令左子树指向翻转后的右子树,将右子树指向翻转后的左子树。
非递归算法的话用先方向层序遍历(从右到左从上到下),然后再正常层序重新赋值即可。
# C++
``` C++
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root == NULL) return NULL;
TreeNode *tmp = root -> left;
root -> left = invertTree(root -> right);
root -> right = invertTree(tmp);
return root;
}
};
```
+21
View File
@@ -0,0 +1,21 @@
# [231. Power of Two](https://leetcode.com/problems/power-of-two/description/)
# 思路
求n是否是2的幂。
1. 首先若n不是正数肯定直接返回false,若n为1直接返回true
2. 若n是不为1的奇数返回false
3. 令n = n / 2,若n=1则返回true否则返回第2步。
# C++
```
class Solution {
public:
bool isPowerOfTwo(int n) {
if(n <= 0) return false;
if(n == 1) return true;
while(n > 1){
if(n % 2 == 1) return false;
n /= 2;
}
return true;
}
};
```
@@ -0,0 +1,62 @@
# [232. Implement Queue using Stacks](https://leetcode.com/problems/implement-queue-using-stacks/description/)
# 思路
用栈实现队列。
设置两个栈stk1和stk2,stk1中的元素是按照正常入栈顺序排的,stk2则是逆序,且同一时刻stk1和stk2至少一个为空。
若stk1不空,对队列入队的话直接对stk1入栈即可,否则要将stk2中所有元素pop到stk1中后再对stk1入栈。
若stk2不空,对队列出队的话直接对stk2出栈即可,否则要将stk1中所有元素pop到stk2中后再对stk2出栈。
# C++
```
class MyQueue {
private:
stack<int>stk1;
stack<int>stk2;
public:
/** Initialize your data structure here. */
MyQueue() {
}
/** Push element x to the back of queue. */
void push(int x) {
if(stk1.empty()){
int tmp;
while(!stk2.empty()){
tmp = stk2.top();
stk2.pop();
stk1.push(tmp);
}
}
stk1.push(x);
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
int tmp;
if(stk2.empty()){
while(!stk1.empty()){
tmp = stk1.top();
stk1.pop();
stk2.push(tmp);
}
}
tmp = stk2.top();
stk2.pop();
return tmp;
}
/** Get the front element. */
int peek() {
if(stk2.empty()){
int tmp;
while(!stk1.empty()){
tmp = stk1.top();
stk1.pop();
stk2.push(tmp);
}
}
return stk2.top();
}
/** Returns whether the queue is empty. */
bool empty() {
return stk1.empty() && stk2.empty();
}
};
```
+53
View File
@@ -0,0 +1,53 @@
# [234. Palindrome Linked List](https://leetcode.com/problems/palindrome-linked-list/description/)
# 思路
判断所给链表是不是回文的,要求线性时间复杂度且空间复杂度为O(1)。
可以考虑现将链表的后半部分(或前半部分)反转一下,然后再设置两个指针p和q,初始分别指向前、后半部分的第一个节点,然后同时往后移动并判断值是否相等。
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
ListNode *p = head, *q, *tmp;
int count = 0;
while(p){
count++;
p = p -> next;
}
if(count <= 1) return true; // 若链表长度不超过1则肯定是回文的
p = head;
for(int i = 1; i < count/2; i++) p = p -> next;
if(count % 2 == 1) p = p -> next; // 链表长为奇数,p需要再往后移动一个节点
// 此时p指向后半部分的第一个节点的前一个节点
// 迭代法翻转后半部分链表
q = p -> next; // 此时q指向后半部分的第一个节点
p -> next = NULL;
while(q){
tmp = q -> next;
q -> next = p -> next;
p -> next = q;
q = tmp;
}
q = p -> next; // q指向后半部分的第一个节点
p = head; // p指向前半部分的第一个节点
for(int i = 0; i < count/2; i++){
if(p -> val != q -> val) return false;
p = p -> next;
q = q -> next;
}
return true;
}
};
```
@@ -0,0 +1,31 @@
# [235. Lowest Common Ancestor of a Binary Search Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/)
# 思路
求一棵搜索树(BST)中两个节点的最低公共祖先( lowest common ancestor, LCA).
求二叉树中节点的最低公共祖先比较麻烦(要用到非递归的后序遍历算法),但是这里是搜索树,由于搜索树中的节点是排好序的,所以这题就简单了。
若有前提p < q, 那么一共有三种情况:
* 若p > root, 说明LCA应该在root的右子树内;
* 若q < root, 说明LCA应该在root的左子树内;
* 否则,即 p < root < q, 那么root就是LCA。(递归出口)
由此可见很容易写出一个递归算法。不过也可以很容易用while循环实现,见下面的代码。
# C++
``` C++
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode *tmp;
if(p -> val > q -> val){ // 保证 p -> val <= q -> val
tmp = q;
q = p;
p = tmp;
}
tmp = root;
while(tmp){
if(p -> val > tmp -> val) tmp = tmp -> right;
else if(q -> val < tmp -> val) tmp = tmp -> left;
else return tmp;
}
}
};
```
@@ -0,0 +1,54 @@
# [237. Delete Node in a Linked List](https://leetcode.com/problems/delete-node-in-a-linked-list/description/)
# 思路
首先要搞清楚题目的意思,题意是给定某个未知链表中的一个节点node,删除这个节点。
## 思路一
由于我们不知道这个链表,那么也没法知道node的前一个节点,也就没法按照常规方法删除node。
但是我们知道node及其之后的节点,而删除之后的链表相当于node之后的每个节点的值都向前移动了一个单位。
## 思路二(思路一改进)
为什么要移动所有节点的值呢,我们仅仅需要移动node后面第一个元素值就可以了。即令node -> val = node -> next -> val, 然后删除node->next即可。
# C++
## 思路一
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
ListNode *pre;
while(node -> next){
node -> val = node -> next -> val;
pre = node;
node = node -> next;
}
pre -> next = NULL;
delete node;
}
};
```
## 思路二
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
ListNode *tmp = node -> next;
node -> val = tmp -> val;
node -> next = tmp -> next;
tmp -> next = NULL;
delete tmp;
}
};
```
+31
View File
@@ -0,0 +1,31 @@
# [24. Swap Nodes in Pairs](https://leetcode.com/problems/swap-nodes-in-pairs/)
# 思路
将一个链表两个两个地进行翻转。
为了方便我们可以设置一个头结点head_node。用p1指向当前需要翻转的第一个节点,p2指向当前需要翻转的第二个节点,并用pre指向p1的前一个节点。
然后交换p1、p2两个节点即可,再将指针向右移进行下一次翻转。
时间复杂度O(n),空间复杂度O(1)
# C++
``` C++
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head == NULL || head -> next == NULL) return head;
ListNode *head_node = new ListNode(0);
head_node -> next = head;
ListNode *pre = head_node, *p1 = head, *p2 = head -> next;
while(p2 != NULL){
// 翻转
p1 -> next = p2 -> next;
p2 -> next = p1;
pre -> next = p2;
// 指针右移,准备进行下一次翻转
pre = p1;
p1 = pre -> next;
if(p1 == NULL) break; // 到链尾了
p2 = p1 -> next;
}
return head_node -> next;
}
};
```
+45
View File
@@ -0,0 +1,45 @@
# [242. Valid Anagram](https://leetcode.com/problems/valid-anagram/description/)
# 思路
题意就是:若s和t由同样的元素组成只是排列顺序不一样则返回true,否则返回false。
## 思路一
用两个长度为26的数组count1和count2分别记录s和t中字母a-z的出现的次数,最后比较两个数组的对应元素值是否相等,若全部相等则返回true,否则返回false;
## 思路二*
思路一的改进,令数组count = count1 - count2,则最后count中所有的元素全为0时返回true否则返回false。
由此可见,只用分配一个数组即可,对s中出现的字母进行次数累加,对t中的出现的字母进行次数累减。
# C++
## 思路一
```
class Solution {
public:
bool isAnagram(string s, string t) {
if(s.size() != t.size()) return false;
vector<int>count1(26), count2(26);
for(int i = 0; i < s.size(); i++){
count1[s[i] - 'a']++;
count2[t[i] - 'a']++;
}
for(int i = 0; i < 26; i++)
if(count1[i] != count2[i]) return false;
return true;
}
};
```
## 思路二
```
class Solution {
public:
bool isAnagram(string s, string t) {
if(s.size() != t.size()) return false;
vector<int>count(26, 0);
for(int i = 0; i < s.size(); i++){
count[s[i] - 'a']++;
count[t[i] - 'a']--;
}
for(int i = 0; i < 26; i++)
if(count[i] != 0) return false;
return true;
}
};
```
+33
View File
@@ -0,0 +1,33 @@
# [257. Binary Tree Paths](https://leetcode.com/problems/binary-tree-paths/description/)
# 思路
递归算法,用一个curr_path记录当前的path,每当遇到叶子节点时就将curr_path push到一个字符串数组里。
# C++
``` C++
class Solution {
private:
vector<string>res;
string curr_path;
void find_leaf(TreeNode* root){
if(root == NULL) return;
if(!curr_path.empty()) curr_path += "->"; // 不是第一个数,应该先加上“->”
curr_path += (to_string(root -> val));
if(root -> right == NULL && root -> left == NULL) res.push_back(curr_path);
find_leaf(root -> left);
find_leaf(root -> right);
// 将当前节点从curr_path中删掉,注意可能是负数所以不能用“-”作为箭头的标志
while(!curr_path.empty() && curr_path[curr_path.size()-1] != '>'){ // 删除最后一个字符直到curr_path空了或者遇到箭头的标志">"
curr_path.erase(curr_path.end() - 1);
}
if(!curr_path.empty()) curr_path.erase(curr_path.size() - 2, 2); // 删除末尾的"->"
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
find_leaf(root);
return res;
}
};
```
+21
View File
@@ -0,0 +1,21 @@
# [258. Add Digits](https://leetcode.com/problems/add-digits/description/)
# 思路
没什么好说的,按照题目的意思,用两个while循环即可。
# C++
```
class Solution {
public:
int addDigits(int num) {
int res = 0;
while(num >= 10){
while(num > 0){
res += (num % 10);
num /= 10;
}
num = res;
res = 0;
}
return num;
}
};
```
@@ -0,0 +1,21 @@
# [026. Remove Duplicates from Sorted Array](https://leetcode.com/problems/remove-duplicates-from-sorted-array/description/)
# 思路
由于是已经排序好的数组,所以相同的数肯定是相邻的,遍历数组时跳过与上一个数重复的数即可。
可以用一个count记录在下标count及之前是没有重复的数组, count初始化为0,然后从前往后遍历,若a[i]==a[count]说明重复,不更新count
否则应该让count++。注意单独判断空数组。
# C++
``` c++
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.empty()) return 0;
int count = 0;
for(int i = 1; i < nums.size(); i++){
if(nums[i] != nums[count]) nums[++count] = nums[i];
}
return count+1;
}
};
```
+18
View File
@@ -0,0 +1,18 @@
# [263. Ugly Number](https://leetcode.com/problems/ugly-number/description/)
# 思路
判断一个整数是否是‘丑陋的’。若一个整数是正数且其质因子仅包括2、3、5, 那么这个数是‘丑陋的’。特例,1也是’丑陋的‘。
首先若n非正,则直接返回false。否则将其分别除以2、3、5直到不能除进,若最后的结果是1则返回true否则返回false。
# C++
```
class Solution {
public:
bool isUgly(int num) {
if(num <= 0) return false;
while(num % 2 == 0) num /= 2;
while(num % 3 == 0) num /= 3;
while(num % 5 == 0) num /= 5;
if(num == 1) return true;
return false;
}
};
```
+52
View File
@@ -0,0 +1,52 @@
# [268. Missing Number](https://leetcode.com/problems/missing-number/description/)
# 思路
## 思路一
先将数组进行排序,然后从前往后遍历,若某元素与其下标不等,说明缺失了下标这个数。
时间复杂度O(nlogn)
## 思路二
因为没有缺失的数组的和是很容易知道的,用这个和减去实际和就是缺失值。
想知道没有缺失的数组的和,先应该找出数组的最大值max_num,若最大值为size-1,则缺失的就是size这个值,直接返回即可。
否则缺失值就为 `result = (max_num + 1)*max_num/2 - sum `
注意上式子第一项很容易存在溢出,所以将上式子拆成 `2*result = ∑(max_num + 1 - 2 * nums[i])`, 这样就不容易溢出了。
时间复杂度O(n), 空间复杂度O(1)
## 思路三
使用一个例子来看一下算法的运算过程:
nums: 0 1 2 4 5 6 7 (缺失3)
index: 0 1 2 3 4 5 6
可见如果再加上一个nums.size()和缺失值的话,所有的数字都是成对的,对这些数字进行异或操作会最终会得0。
所以如果不加缺失值,异或得到的结果就是缺失值。
此算法就肯定不会溢出了, 时间复杂度O(n), 空间复杂度O(1)
# C++
## 思路二
```
class Solution {
public:
int missingNumber(vector<int>& nums) {
int max_num = -1;
for(int num: nums)
if(max_num < num) max_num = num;
if(max_num == nums.size() - 1)
return nums.size();
int result_2 = 0;
for(int num: nums)
result_2 += (max_num + 1 - 2 * num);
return result_2 / 2;
}
};
```
## 思路三
```
class Solution {
public:
int missingNumber(vector<int>& nums) {
int result = 0, i = 0;
for (i = 0; i < nums.size(); i++) {
result = result ^ i ^ nums[i];
}
return result ^ nums.size();
}
};
```
+15
View File
@@ -0,0 +1,15 @@
# 思路
类似第26题, 用count记录非val的个数,从前往后遍历,如果值为val则跳过,否则令nums[count并=nums[i]并自增count
# C++
```
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int count = 0;
for(int i = 0; i < nums.size(); i++){
if(nums[i] != val) nums[count++] = nums[i];
}
return count;
}
};
```
+24
View File
@@ -0,0 +1,24 @@
# [278. First Bad Version](https://leetcode.com/problems/first-bad-version/description/)
# 思路
思路很简单,就是二分法。
但是这题会超时,其实问题不是时间复杂度高(二分法的时间复杂度已经是理论最低了),而是因为在计算`mid = (low + high) / 2`时low + high会溢出而产生不可预料的值。
所以,我们不应该用`mid = (high + low) / 2`来更新mid而应该`mid = low + (high - low) / 2`
> **以后的二分法都应该这样更新mid以防溢出!!!**
# C++
```
// Forward declaration of isBadVersion API.
bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int low = 1, high = n, mid;
while(low <= high){
mid = low + (high - low) / 2; // mid = (high + low) / 2 会溢出!!!
if(isBadVersion(mid)) high = mid - 1;
else low = mid + 1;
}
return low;
}
};
```
+20
View File
@@ -0,0 +1,20 @@
# [283. Move Zeroes](https://leetcode.com/problems/move-zeroes/description/)
# 思路
题意就是将所有的0移到数组最后,要求非0元素的相对顺序不变。
因为要求非0元素相对位置不变,所以从后往前遍历遇到非0元素就前移合适的位置即可。
为了找到这个合适的位置,用变量not_0记录当前元素之前有多少非0元素,若当前元素也是非0元素,则将该元素移到下标为not_0位置即可。
时间复杂度O(n),空间复杂度O(1)
# C++
```
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int not_0 = 0;
for(int i = 0; i < nums.size(); i++)
if(nums[i] != 0)
nums[not_0++] = nums[i];
for(int i = not_0; i < nums.size(); i++)
nums[i] = 0;
}
};
```
+48
View File
@@ -0,0 +1,48 @@
# [29. Divide Two Integers](https://leetcode.com/problems/divide-two-integers/)
# 思路
不使用取模、除法等操作求两数相除的商。
常规思路就是不断循环用被除数减去除数然后记录被减的次数即商,但是亲测会超时。
既然每次减去除数会超时,那么一次性地减去除数的2倍、4倍、8倍......(之所以取2的次方倍是为了移位操作即可快速实现)呢,基于这个思路,我们有以下算法:
假设除数和被除数都是正数,例如考虑15除3,先用15-3发现结果等于12>3,再尝试用15-6发现结果得9>3,再尝试用15-12发现结果3=3,再尝试用15减去24发现不够减,
则应该用15-12然后余3,此时商得4;然后再考虑3除3,依然用上面的思路,得到商为1,最终的结果就是将每一步得到的商相加即可。
如果是负数的话先取绝对值最后判断符号即可。
另外需要考虑两个溢出的情况:
* `dividend == INT_MIN && divisor == -1`,结果是`-INT_MIN`超出了int的表示范围。
* ·dividend == INT_MIN && divisor == 1`,结果是`INT_MIN`虽然没有超过范围,但是我们是按照绝对值计算结果的,所以计算过程res也会溢出。
另外需要注意的是再用abs(x)求绝对值的时候一定要先将x转换成long long型这样abs(x)返回的才是long long型,保证不溢出。否则若x=INT_MIN的话求绝对值就超过
int型表示范围了。
[参考](https://leetcode.com/problems/divide-two-integers/discuss/13407/Detailed-Explained-8ms-C%2B%2B-solution)
# C++
``` C++
class Solution {
public:
int divide(int dividend, int divisor) {
if(dividend == 0) return 0;
if(dividend == INT_MIN && divisor == -1) return INT_MAX;
if(dividend == INT_MIN && divisor == 1) return INT_MIN;
int res = 0;
long long dvd = abs((long long)dividend); // 一定要先将dividend和divisor转换成long long型这样abs才返回long long型
long long dvs = abs((long long)divisor);
long long dvs_bk = dvs; // 除数备份
while(dvd >= dvs){
int curr = 1;
dvs <<= 1;
while(dvd - dvs >= 0){
curr *= 2;
dvs <<= 1;
} // dvd < dvs
dvs >>= 1;
dvd -= dvs;
dvs = dvs_bk;
res += curr;
}
if(dividend > 0 && divisor < 0) res *= -1;
else if(dividend < 0 && divisor > 0) res *= -1;
return res;
}
};
```
+34
View File
@@ -0,0 +1,34 @@
# [290. Word Pattern](https://leetcode.com/problems/word-pattern/description/)
# 思路
类似[205. Isomorphic Strings](https://leetcode.com/problems/isomorphic-strings/description/), 所以解题思路基本一样。
将pattern和str中个每个元素都用一个数代替,这个数代表了该元素是第几个出现的(如 "abba" -> 1221, "dog cat cat dog" -> 1221), 则结果应该是一样的。
为了记录是否出现过,pattern用一个长度为26的数组实现,str用map来实现,此外还用一个count计数。
# C++
```
class Solution {
public:
bool wordPattern(string pattern, string str) {
map<string, int>mp;
vector<int>vc(26, 0);
string sub_str;
int count = 1, pos = 0, pre; // pos记录空格的下一个位置,pre为上一个空格位置的下一个位置
for(int i = 0; i < pattern.size(); i++){
if(pos >= str.size()) return false; // str中元素少于pattern中的元素
pre = pos;
while(str[pos] != ' ' && pos < str.size()) pos++; // 此时pos为空格的位置或str结束位置
// s.substr(pos1,n)返回字符串s从pos1开始n个字符组成的串
sub_str = str.substr(pre, pos - pre);
pos++; // pos记录空格的下一个位置
// 以下代码基本同205题
if(vc[pattern[i] - 'a'] != mp[sub_str]) return false;
if(vc[pattern[i] - 'a'] == 0){
vc[pattern[i] - 'a'] = count;
mp[sub_str] = count++;
}
}
if(pos != str.size() + 1) return false; // str中元素多余pattern中的元素
return true;
}
};
```
+19
View File
@@ -0,0 +1,19 @@
# [292. Nim Game](https://leetcode.com/problems/nim-game/)
# 思路
A、B人交替数数,每个人每次只能数1、2、或3个数,谁先数到n谁赢,A先数。AB两人都想自己赢。
* 若n = 1、2或3, 那么A肯定赢;
* 若n = 4, 那么不管A数1、2还是3个数A都会输;
* 若n = 5、6或7, 那么A可以分别数1、2和3个数然后转换成n = 4且B先数的情况,则最终A赢;
* 若n = 8, 那么不管A数完第一次后n = 5、6、7,然后该B数,由上条分析知最终A输;
* ......
由此可见,当n是4的倍数时A会输,否则赢。
# C++
``` C++
class Solution {
public:
bool canWinNim(int n) {
return !(n % 4 == 0);
}
};
```
@@ -0,0 +1,58 @@
# [3. Longest Substring Without Repeating Characters](https://leetcode.com/problems/longest-substring-without-repeating-characters/)
# 思路
## 思路一、暴力动归
用一个与s等长数组dp记录以某个字符结尾的最长不重复子串的长度,即`dp[i]=m`表示以s[i]结尾的最长不重复子串的长度为m。所以dp初始化全为1.
dp[i]应该这样计算:
* 从s[i-1]开始往前遍历dp[i-1]个字符,若中途遍历到某个字符与s[i]相等了则跳出循环。若往前遍历了tmp(从0开始)个字符,则`dp[i] += tmp`
时间复杂度O(n*n),空间复杂度O(n)
## 思路二、窗口法
用一个hash map(这里直接用一个长为128数组就行了,因为ASCII就是0-127)mp记录某个字符c在s中的位置,初始全-1.
用left、i记录当前窗口左、右边的位置,窗口[left ~ i]中保证是无重复的。
left和i初始都为0,i从0开始不断加1并按下面步骤循环:
1. 设当前字符为c,若 `mp[c] >= left`,则字符c第二次出现在了窗口中,left即将右移,先更新res再将left移动到上一个c的下一个位置;否则什么都不做。
2. 最后更新mp: ` mp[c] = i`
时间复杂度O(n),空间复杂度O(1)
# C++
## 思路一
``` C++
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if(s.size() == 0) return 0;
vector<int>dp(s.size(), 1);
int res = 1;
for(int i = 1; i < s.size(); i++){
int tmp = 0;
for(; tmp < dp[i - 1]; tmp++){ // 往前遍历dp[i - 1]个字符
if(s[i] == s[i - 1 - tmp]) break;
}
dp[i] = dp[i] + tmp;
res = (dp[i] > res ? dp[i]:res);
}
return res;
}
};
```
## 思路二
``` C++
class Solution {
public:
int lengthOfLongestSubstring(string s) {
vector<int>mp(128, -1); // 记录s中某个字符所在位置,初始为-1 (数组大小设置成256貌似快一些,不知道为啥)
int left = 0, res = 0, i = 0; // 窗口[left ~ i]中保证是无重复的
for(; i < s.size(); i++){
char c = s[i];
if(mp[c] >= left) { // 字符c第二次出现在了窗口中,left即将右移,先更新res
res = max(res, i - left);
left = mp[c] + 1; // left右移到上一个c所在位置的下一个位置
}
mp[c] = i; // 当前字符c的位置
}
return max(res, i - left);
}
};
```
@@ -0,0 +1,24 @@
# [303. Range Sum Query - Immutable](https://leetcode.com/problems/range-sum-query-immutable/description/)
# 思路
用一个数组sums记录和,sums[i]代表nums[0]到nums[i]的和,那么sumRange(i, j)就应该等于sum[i] - sum[i-1], 注意单独判断i得0时。
注意这种面向对象的代码风格。
# C++
```
class NumArray {
private:
vector<int> sums;
public:
NumArray(vector<int> nums) {
int tmp = 0;
for(int num: nums) {
tmp += num;
sums.push_back(tmp);
}
}
int sumRange(int i, int j) {
if(i == 0) return sums[j];
else return sums[j] - sums[i - 1];
}
};
```
+47
View File
@@ -0,0 +1,47 @@
# [31. Next Permutation](https://leetcode.com/problems/next-permutation/)
# 思路
题意就是实现标准库里的next_permutation函数(不考虑自定义comp的情况)。
我们先来看看这个函数,以后可以直接用:
> Transforms the range [first, last) into the next permutation from the set of all permutations that are lexicographically ordered with respect to operator< or comp.
Returns true if such permutation exists, otherwise transforms the range into the first permutation (as if by std::sort(first, last)) and returns false.
`bool next_permutation (first, last)`返回的是bool型,如果已经是最后一个排列了则返回false并将数组变成第一个排列(即按照从小到大排好序);否则返回true并将数组变成下一个排列。
此外还可以传入`comp`参数: `next_permutation (first, last, comp)`这样就可以自定义数组的大小规则。
下面来分析一下如何实现:
首先应该知道何为下一个排列,若当前排列组成的数为A,则下一个排列组成的数B就是刚好比A大(即没有另外一个排列组成的数C满足A<C<B)的排列。例如02431的下一个排列是03124,03124的下一个排列是03142。。。
可见我们总是贪心地从最低位考虑,找到第一个不满足从低到高位逐渐增大的数,例如02431从最低位1到高位不满足逐渐增大的第一个数就是2,说明2以后的三个数(431)已经是这三个数能
组成的最大的数了,所以下一个排列肯定要把2考虑进去,而2之前的0就不用考虑了。既然要把2考虑进去,就说明要用一个数来替换2,为了使B刚好比A大,则应该取431中比2大的最小的那个数字即3,
然后将3和2替换得到3421,然后再将421 reverse得到3124即可。3124是刚好比2431大的排列。
注意:
* 已经是最后一个排列时,需要单独考虑。
* 交换两个数直接用swap要比手动实现快不少。
时间复杂度O(n), 空间复杂度O(1)
# C++
```C++
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int len = nums.size();
int i, j;
for(i = len - 1; i > 0; i--){
if(nums[i] > nums[i - 1]) break;
}
if(i == 0) { // 已经是最后一个排列时,需要单独考虑。
reverse(nums.begin(), nums.end());
return;
}
for(j = len - 1; j >= i; j--){
if(nums[j] > nums[i - 1]) break;
}
swap(nums[j], nums[i-1]); // 用swap击败99%, 若按照下面代码手动实现则击败16%
// int tmp = nums[j];
// nums[j] = nums[i - 1];
// nums[i - 1] = tmp;
reverse(nums.begin() + i, nums.end());
}
};
```
+18
View File
@@ -0,0 +1,18 @@
# [326. Power of Three](https://leetcode.com/problems/power-of-three/description/)
# 思路
题目求给定数是否是3的幂。而且要求不能运用循环和递归。
由于3是质数,所以若对n进行质因子分解可以得到3^k的形式,则是3的幂。因此,用一个很大的3的幂(3^M)除以n,
若能除进,即3^M = m * n,则n是3的幂(因为3^M分解因子只能是3^x形式),否则则不是。很大的3的幂可以设置成int型不溢出的最大的3的幂。
**以上方法适用于判断某数是否是某个质数(如2、3、5..)的幂的问题!!!**
更多解法参考[此处](https://leetcode.com/problems/power-of-three/discuss/77876/**-A-summary-of-all-solutions-(new-method-included-at-15:30pm-Jan-8th))
# C++
```
class Solution {
public:
bool isPowerOfThree(int n) {
// int maxPowerOf3 = (int)pow(3, (int)(log(INT_MAX) / log(3))); // = 1162261467;
int maxPowerOf3 = 1162261467;
return n > 0 && maxPowerOf3 % n == 0;
}
};
```
+45
View File
@@ -0,0 +1,45 @@
# [342. Power of Four](https://leetcode.com/problems/power-of-four/description/)
# 思路
判断一个整数是否是4的幂,要求不能用循环或者递归。
## 思路一
我们知道4的幂有1、4、16、64......,将其转换为二进制有:
* 1 -> 00000001
* 4 -> 00000100
* 16 -> 00010000
* ........
可以发现一个正数是4的幂的充要条件是:
* 1、其二进制只有一个1;
* 2、且1的位置从低位起只能是位于0、2、4...(即全为偶数)处。
第1个条件等价于去掉最后的那个1后整个数变为0,即 `num & (num - 1) == 0`;(**注意学习这种去掉二进制最后一个1的方法**)
第2个条件等价于 `(num | mask) == mask`, 其中`mask = 0b01010101010101010101010101010101`
## 思路二
还是基于思路一的两个条件,我们知道如果只满足思路一的条件1的数可能是2^n也可能是4^n, 怎样排除掉2^n呢?
我们知道:
<img src="https://latex.codecogs.com/svg.latex?\Large&space;&&2^n=(3-1)^n=C_n^03^0(-1)^n+C_{n-1}^13^1(-1)^{n-1}+......+C_n^n3^n(-1)^0&&" />
所以,
* 1.n为偶数时既是2的幂也是4的幂,此时(-1)^n==1所以(2^n-1% 3==0;
* 2.n为奇数是只是2的幂但不是4的幂, 此时(-1)^n=-1所以(2^n-1% 3==1
故可以用(2^n-1)% 3是否等于0来等价判断思路一的条件2, 将2的幂和4的幂区分开。
# C++
## 思路一
``` C++
class Solution {
public:
bool isPowerOfFour(int num) {
int mask = 0b01010101010101010101010101010101;
return num > 0 && (num & (num - 1)) == 0 && (num | mask) == mask;
}
};
```
## 思路二
``` C++
class Solution {
public:
bool isPowerOfFour(int num) {
return num > 0 && (num & (num - 1)) == 0 && (num - 1) % 3 == 0;
}
};
```
+18
View File
@@ -0,0 +1,18 @@
# [344. Reverse String](https://leetcode.com/problems/reverse-string/description/)
# 思路
翻转字符串。
注意:交换元素时,最好用标准库里的swap,快一些。
# C++
```
class Solution {
public:
string reverseString(string s) {
int low = 0, high = s.size() - 1;
while(low < high){
swap(s[low++], s[high--]); // 库里的swap比自己写的要快一些
}
return s;
}
};
```
@@ -0,0 +1,24 @@
# [345. Reverse Vowels of a String](https://leetcode.com/problems/reverse-vowels-of-a-string/description/)
# 思路
翻转字符串中的元音字母,即A、E、I、O、U、a、e、i、o、u。常规题
# C++
```
class Solution {
private:
bool isVowel(char c){
if('A' <= c && c <= 'Z') c = c - 'A' + 'a';
if(c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') return true;
return false;
}
public:
string reverseVowels(string s) {
int low = 0, high = s.size() - 1;
while(low < high){
while(low < high && !isVowel(s[low])) low++;
while(low < high && !isVowel(s[high])) high--;
if(low < high) swap(s[low++], s[high--]);
}
return s;
}
};
```
@@ -0,0 +1,31 @@
# [349. Intersection of Two Arrays](https://leetcode.com/problems/intersection-of-two-arrays/description/)
# 思路
题目欲求两个数组的交集,注意集合里元素都是唯一的。可以考虑将两个数组进行排序,再用两个指针分别遍历两个有序数组,合理更新指针即可获取两个数组的重复元素。
为了没有重复的元素,可以设置一个pre来记录上一个相同的元素是什么。
时间复杂度O(nlogn)
当然也可以用map来查看是否有相同元素。
# C++
```
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
vector<int>res;
int p1 = 0, p2 = 0, pre;
while(p1 < nums1.size() && p2 < nums2.size()){
if(nums1[p1] < nums2[p2]) p1++;
else if(nums1[p1] > nums2[p2]) p2++;
else{
if(res.empty() || pre != nums1[p1]){
res.push_back(nums1[p1]);
pre = nums1[p1];
}
p1++;
p2++;
}
}
return res;
}
};
```
+16
View File
@@ -0,0 +1,16 @@
# [Search Insert Position](https://leetcode.com/problems/remove-element/description/)
# 思路
排序数组查找肯定就是二分法啦,但是题目没说一定是增序,根据结果来看应该全是增序。
# C++
```
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int count = 0;
for(int i = 0; i < nums.size(); i++){
if(nums[i] != val) nums[count++] = nums[i];
}
return count;
}
};
```
@@ -0,0 +1,27 @@
# [350. Intersection of Two Arrays II](https://leetcode.com/problems/intersection-of-two-arrays-ii/description/)
# 思路
基本同[349. Intersection of Two Arrays](https://leetcode.com/problems/intersection-of-two-arrays/description/), 只是题结果中可以出现重复元素,
则先对两个数组进行排序,然后再用两个指针遍历一遍数组,合理更新指针即可获得两个数组的相同元素。
时间复杂度O(nlogn)
# C++
```
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
vector<int>res;
int p1 = 0, p2 = 0;
while(p1 < nums1.size() && p2 < nums2.size()){
if(nums1[p1] < nums2[p2]) p1++;
else if(nums1[p1] > nums2[p2]) p2++;
else{
res.push_back(nums1[p1]);
p1++;
p2++;
}
}
return res;
}
};
```
+22
View File
@@ -0,0 +1,22 @@
# [367. Valid Perfect Square](https://leetcode.com/problems/valid-perfect-square/description/)
# 思路
题目要求判断一个数是否是一个某个数的平方。要求不能调用库里的sqrt函数。
可以考虑二分搜索1到num/2范围的数mid, 判断mid的平方是否等于num。
注意:如果直接计算`if(mid * mid == num)` 会存在溢出的情况,所以应该`if(num % mid == 0 && num / mid == mid)`
# C++
```
class Solution {
public:
bool isPerfectSquare(int num) {
if(num == 1) return true;
int low = 1, high = num / 2, mid;
while(low <= high){
mid = (low + high) / 2;
if(num % mid == 0 && num / mid == mid) return true;
if(mid <= num / mid) low = mid + 1;
else high = mid - 1;
}
return false;
}
};
```
+31
View File
@@ -0,0 +1,31 @@
# [371. Sum of Two Integers](https://leetcode.com/problems/sum-of-two-integers/description/)
# 思路
求两个数的和,要求不能使用操作符+、-。
我们知道计算机底层中的四则运算都是用门电路通过简单的逻辑操作实现的, 例如对于一位的二进制加法:
| a | b | sum(和) | cin(进位) |
| :-: | :-: | :-: | :-: |
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
所以一位加法规则是: `sum = a ^ b, cin = a & b`.
对于多位的加法,我们可以先分别求出sum和cin, 再用sum和左移一位的cin相加求出新的sum和cin, 循环直到cin为0.
# C++
``` C++
class Solution {
public:
int getSum(int a, int b) {
int res = a ^ b, cin = a & b; // 异或操作'^': 相同为0不同为1
while(cin != 0){
a = cin << 1;
b = res;
res = a ^ b;
cin = a & b;
}
return res;
}
};
```
@@ -0,0 +1,24 @@
# [374. Guess Number Higher or Lower](https://leetcode.com/problems/guess-number-higher-or-lower/description/)
# 思路
二分法。
注意计算mid的时候防止溢出,见代码。
# C++
```
// Forward declaration of guess API.
// @param num, your guess
// @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
int guess(int num);
class Solution {
public:
int guessNumber(int n) {
int low = 1, high= n, mid;
while(low <= high){
mid = low + (high - low) / 2; // 不用 mid = (low + high) / 2, 因为这样可能溢出
if(guess(mid) == 0) return mid;
else if(guess(mid) > 0) low = mid + 1;
else high = mid - 1;
}
}
};
```
+32
View File
@@ -0,0 +1,32 @@
# [38. Count and Say](https://leetcode.com/problems/count-and-say/description/)
# 思路
首先要搞清楚题目意思,初试串为"1",按照数数的规则产生后面的序列:
* 第1个字符串为"1" 即1个1,所以第2个字符串为"11";
* 第2个字符串为"11" 即2个1,所以第3个字符串为"21";
* 第3个字符串为"21", 即1个2和1个1,所以第4个字符串为"1211";
* 第4个字符串为"1211" 即1个1、1个2和2个1,所以第2个字符串为"111221";
* ......
搞懂意思后,按照规则模拟即可。
# C++
```
class Solution {
public:
string countAndSay(int n) {
string res = "1";
while(n-- > 1){ // 循环产生第n个串
string tmp;
int low=0, high=1;
while(low < res.size()){
while(high < res.size() && res[high] == res[low]) high++;
tmp += (high - low + '0');
tmp += res[low];
low = high;
high++;
}
res = tmp;
}
return res;
}
};
```
+16
View File
@@ -0,0 +1,16 @@
# [383. Ransom Note](https://leetcode.com/problems/ransom-note/description/)
# 思路
用一个大小为26的数组count记录magazine中26个字母的出现次数,只要每个字母出现次数不小于ransomNote对应字母次数就行了。
# C++
```
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
vector<int>count(26);
for(char c: magazine) count[c - 'a']++;
for(char c: ransomNote)
if(0 > --count[c - 'a']) return false;
return true;
}
};
```
@@ -0,0 +1,18 @@
# [387. First Unique Character in a String](https://leetcode.com/problems/first-unique-character-in-a-string/description/)
# 思路
先遍历一遍字符串,记录每个字符出现的次数,再遍历一遍找出第一个没有重复的字符。
由于题目说了全部字符都是小写字母,所以开辟一个大小为26的数组记录字符出现次数即可。
时间复杂度O(n)
# C++
```
class Solution {
public:
int firstUniqChar(string s) {
vector<int>count(26, 0);
for(int i = 0; i < s.size(); i++) count[s[i] - 'a']++;
for(int i = 0; i < s.size(); i++)
if(count[s[i] - 'a'] == 1) return i;
return -1;
}
};
```
+22
View File
@@ -0,0 +1,22 @@
# [389. Find the Difference](https://leetcode.com/problems/find-the-difference/description/)
# 思路
分别记录两个字符串中每个字符的出现次数,最后比较一下字符的出现次数,次数差1对应的字符即所求。
因为全是小写字母,所以开辟一个大小为26的数组进行计数即可。另外,没必要开辟两个计数数组,用一个数组记录即可,对s中出现的字符进行次数累加,对t中出现的字符进行次数累减。
最后次数为-1的对应的字母即所求。
时间复杂度O(n), 空间复杂度O(1)
# C++
```
class Solution {
public:
char findTheDifference(string s, string t) {
vector<int>count(26, 0);
for(int i = 0; i < s.size(); i++){
count[s[i] - 'a']++;
count[t[i] - 'a']--;
}
count[t[t.size() - 1] - 'a']--;
for(int i = 0; i < 26; i++)
if(count[i] == -1) return char(i + 'a');
}
};
```
+37
View File
@@ -0,0 +1,37 @@
# [400. Nth Digit](https://leetcode.com/problems/nth-digit/description/)
# 思路
计算序列1、2、3、4、5、6、7、8、9、10、11...中第n个数字(digit,注意不是数)是什么。
我们可以将这个序列分成很多类:
* 第一类:1、2、3...9共9(设为base,下同)个数, 即每个数只包含1(设为k,下同)个数字,一共有base * k = 9 个数字;
* 第二类:10、11、12...99base = 90k = 2;
* 第三类:100、101、102...999,base = 900k = 3;
* ......
设所求的digit落在数target上,则我们可根据上述规律算出target具体是多少,然后再算出所求具体是target的哪一个digit。
注意:
当我们算base * k 的时候,结果可能超过int的表示范围,所以要定义成long long型。
# C++
```
class Solution {
public:
int findNthDigit(int n) {
if(n < 10) return n;
// 计算target
long long base = 9, k = 1;
while(n > base * k){
n -= base * k;
base *= 10;
k++;
}
int target = base / 9 + (n - 1) / k;
// 计算所求digit具体是target的哪一位
while(n % k != 0) {
target /= 10;
n++;
}
return target % 10;
}
};
```
+49
View File
@@ -0,0 +1,49 @@
# [404. Sum of Left Leaves](https://leetcode.com/problems/sum-of-left-leaves/description/)
# 思路
计算一棵二叉树所有左叶子的值之和。
## 思路一
设置一个初始为0的全局变量res,遍历一遍树,判断某节点的左子树是否是叶子,若是,则将这个叶子的值加至res,遍历完后返回res即可。
## 思路二
直接递归计算结果res。因为某棵树的res等于其左子树的res加上右子树的res。
# C++
## 思路一
``` C++
class Solution {
private:
int res = 0;
bool is_leaf(TreeNode *p){ // 判断是否是叶子
if(p == NULL) return false;
return (p -> left == NULL && p -> right == NULL);
}
void find_left_leaf(TreeNode *root){
if(root == NULL || is_leaf(root)) return;
if(is_leaf(root -> left)) res += root -> left -> val; // 左子树是叶子,加至res
else find_left_leaf(root -> left);
find_left_leaf(root -> right);
}
public:
int sumOfLeftLeaves(TreeNode* root) {
find_left_leaf(root);
return res;
}
};
```
## 思路二
``` C++
class Solution {
private:
bool is_leaf(TreeNode *p){ // 判断是否是叶子
if(p == NULL) return false;
return (p -> left == NULL && p -> right == NULL);
}
public:
int sumOfLeftLeaves(TreeNode* root) {
if(root == NULL || is_leaf(root)) return 0;
if(is_leaf(root -> left)) return (root -> left -> val) + sumOfLeftLeaves(root -> right);
else return sumOfLeftLeaves(root -> left) + sumOfLeftLeaves(root -> right);
}
};
```
@@ -0,0 +1,53 @@
# [405. Convert a Number to Hexadecimal](https://leetcode.com/problems/convert-a-number-to-hexadecimal/description/)
# 思路
将用补码(2's complement)表示的数转换成16进制数。
## 思路一
进制转换常规思路。
首先将补码表示的数看做是无符号的数,然后再用常规的不断除以16的方法转换进制。
例如将十进制数25转换成16进制:
> 25 / 16 = 1···9
> 1 / 16 = 0···1
> 所以结果就是19
## 思路二
由于2进制转16进制刚好是4位为一组,所以可考虑从低位起不断获取num的四位bit再将其转换成16进制。
例如将十进制数25转换成16进制:
> 25的32位二进制表示为`000..0011001`, 最低四位`1001`的16进制表示为9,次第四位`0001`的16进制表示为1,再高位就全是0了,所以结果就是19.
获得num的低四位可以通过num和mask`0x0000000f`进行按位与操作实现。
# C++
## 思路一
``` C++
class Solution {
public:
string toHex(int num) {
if(num == 0) return "0";
string mp = "0123456789abcdef";
string res;
unsigned n = (unsigned)num;
while(n > 0){
res += mp[n % 16];
n /= 16;
}
reverse(res.begin(), res.end());
return res;
}
};
```
## 思路二
``` c++
class Solution {
public:
string toHex(int num) {
if(num == 0) return "0";
string mp = "0123456789abcdef";
string res;
while(num != 0){
res += mp[0x0000000f & num];
num >>= 4;
}
reverse(res.begin(), res.end());
return res;
}
};
```
+26
View File
@@ -0,0 +1,26 @@
# [409. Longest Palindrome](https://leetcode.com/problems/longest-palindrome/description/)
# 思路
给出一些包含大小写的字母,判断能由这些字母组成的回文串最长为多少。
分析:如果某个字母出现次数为偶数,那肯定全部都能放进目标最长的回文串,如果某个字母出现次数为奇数,则只能放下不超过这个奇数的最大偶数(也就是减1)个该字母。
不过需要注意的是,如果出现了出现次数为奇数的字母,则最后的回文串长度按照上诉思路计算后还应该加1,此时回文串长度为奇数,例如dccaccd。
时间复杂度O(n), 空间复杂度O(1)
# C++
```
class Solution {
public:
int longestPalindrome(string s) {
vector<int>count(52, 0);
for(int i = 0; i < s.size(); i++){
if(s[i] >= 'a' && s[i] <= 'z') count[s[i] - 'a']++;
else count[s[i] - 'A' + 26]++;
}
int odd_tag = 0, res = 0;
for(int n: count){
if(n % 2 == 1) odd_tag = 1;
res += (n - n % 2);
}
res += odd_tag;
return res;
}
};
```
+27
View File
@@ -0,0 +1,27 @@
# [414. Third Maximum Number](https://leetcode.com/problems/third-maximum-number/description/)
# 思路
题意就是找出数组第三大的数,注意相同的数算作一个数,可以考虑遍历三次,第一次得到最大的数max1,第二次得到第二大的数max2,第三次就得到了第三大的数max3。
注意可以用INT_MIN(在limits.h里)表示int型最小的数,另外需要用一个tag记录是否找到max3,否则当max3==INT_MIN时无法判断max3是否是真的第三大的值还是初始值。
# C++
```
class Solution {
public:
int thirdMax(vector<int>& nums) {
int max1 = INT_MIN, max2 = INT_MIN, max3 = INT_MIN, tag = 0;
for(int num: nums)
if(max1 <= num)
max1 = num;
for(int num: nums)
if(max2 <= num && num != max1)
max2 = num;
for(int num: nums)
if(max3 <= num && num != max1 && num != max2){
max3 = num;
tag = 1;
}
if(tag == 0) return max1;
return max3;
}
};
```
+30
View File
@@ -0,0 +1,30 @@
# [415. Add Strings](https://leetcode.com/problems/add-strings/description/)
# 思路
题目要求计算两个大数的和。
可以先定义一位的加法digit_add,然后再从后往前遍历num1和num2并不断调用digit_add即可完成大数相加。
# C++
```
class Solution {
private:
int digit_add(const int d1, const int d2, int &cin){
int sum = d1 + d2 + cin;
cin = sum / 10;
return sum % 10;
}
public:
string addStrings(string num1, string num2){
vector<char>res;
int cin = 0;
int i1 = num1.size() - 1, i2 = num2.size() - 1;
while(i1 >= 0 && i2 >= 0){
res.push_back('0' + digit_add(num1[i1--] - '0', num2[i2--] - '0', cin));
}
// 以下两个循环至多执行一个
while(i1 >= 0) res.push_back('0' + digit_add(num1[i1--] - '0', 0, cin));
while(i2 >= 0) res.push_back('0' + digit_add(num2[i2--] - '0', 0, cin));
if(cin != 0) res.push_back(cin + '0');
reverse(res.begin(), res.end());
return string(res.begin(), res.end());
}
};
```
@@ -0,0 +1,42 @@
# [429. N-ary Tree Level Order Traversal](https://leetcode.com/problems/n-ary-tree-level-order-traversal/description/)
# 思路
树的层次遍历。和二叉树的层次遍历其实是一样的。
用last指针指向每一层的最后一个节点,每当遍历到这个节点即说明遍历完一层,
此时应该将此层所有节点(用数组a_level记录)push进保存最终结果的数组res里,然后清空a_level,继续遍历下一层。
last初始为root, 后面每当遍历完每层最后一个节点后,即将last更新成下一层的最后一个节点,为此需要用一个next_last来不断记录能确定的下一层的最右节点。
时间复杂度和空间复杂度都是O(n)
(讨论区有一个[运行时间比较短的递归算法](https://leetcode.com/problems/n-ary-tree-level-order-traversal/discuss/157521/C++-Easy-to-understand-recursive-solution-based-on-DFS-(44-ms-beats-98.67)),但是评论说其复杂度比较高,所以没细看)
# C++
``` C++
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>>res;
if(!root) return res;
queue<Node*>q;
vector<int>a_level;
Node *p, *next_last, *last = root;
q.push(root);
while(!q.empty()){
p = q.front();
q.pop();
a_level.push_back(p -> val);
for(int i = 0; i < p -> children.size(); i++){
next_last = p -> children[i];
q.push(next_last);
}
if(p == last){
res.push_back(a_level);
a_level.clear();
last = next_last;
}
}
return res;
}
};
```
@@ -0,0 +1,21 @@
# [434. Number of Segments in a String](https://leetcode.com/problems/number-of-segments-in-a-string/description/)
# 思路
计算一个字符串被空格分成了多少部分。
从头至尾遍历字符串,用tag标记当前字符的前一个字符是否是空格,若tag=1则前面是空格否则不是。若当前字符不是空格且tag==1则count应该加1。
# C++
```
class Solution {
public:
int countSegments(string s) {
int tag = 1, res = 0;
for(int p = 0; p < s.size(); p++){
if(s[p] == ' ') tag = 1;
else if(tag == 1){ // 当前字符不是空格且tag==1
res++;
tag = 0;
}
}
return res;
}
};
```
+104
View File
@@ -0,0 +1,104 @@
# [437. Path Sum III](https://leetcode.com/problems/path-sum-iii/description/)
# 思路
求二叉树中路径和等于sum的路径条数,这个路径不一定是从根开始以叶子结束,只要求从上到下就行。
## 思路一
最简单的思路,如果这条路径要求必须从根开始,那么这题只需要遍历一遍二叉树就行了。
因此我们考虑遍历一遍二叉树,遍历到节点node时,就将node作为根节点调用从根开始的路径和函数。则算法具体步骤如下:
* 递归遍历root这课树,假设现在遍历到了节点node,运行rootpathSum(node, sum)
* rootpathSum(node, sum)计算了所有从node开始的路径的和等于sum的路径条数;
* 同时用一个全局变量res对结果进行累加。
运用了两次递归,复杂度有点高。
实际跑出来只击败了30%多...
时间复杂度O(n*n)
## 思路二
思路一递归中还包含递归,因此时间复杂度比较高。
其实我们在先序遍历到某个节点node时,从根到此节点路径(设为out)上所有节点都应见过了,
那么我们就应该知道路径out上有多少满足条件且以node结束的子路径了。out定义成一个数组,里面存放了从root到node这条路径的所有节点,再用curSum记录out里面所有
元素的和,每次都要检查out里面是否存在满足条件且以node结束的子路径(一个for循环)。
相对于思路一来说,思路二把思路一的递归改成了循环,运行时间有所下降(击败了80%的人)但是时间复杂度依然是O(n*n)
## 思路三*
不难发现思路二中的每次for循环包含了大量的重复运算,我们可以考虑用空间换时间的思想去掉这些不必要的重复计算。
大致思想还是同思路二,不过不用out数组记录root到node的所有节点,而是用一个hash表mp记录root到(当前)所有节点的每个路径和的个数,这样的话mp[curSum - sum]
就是满足条件的且以node结束的路径条数。
例如,若root到node的值分别是:1、2、3、-3,则4个从root开始的路径和分别为1、3、6、3,那么此时就有`mp[0] = 1``mp[1] = 1``mp[3]=2``mp[6]=1`
若sum=2,则此时得到的满足条件的且以node结束的路径条数就为`mp[curSum - sum] = mp[3 - 2] = 1`, 这条路径就是“2、3、-3”。
此时击败了99%的人,时间复杂度为O(n)
# C++
## 思路一
``` C++
class Solution {
private:
int res = 0;
int rootpathSum(TreeNode *root, int sum){ // 计算从根开始的路径和等于sum的路径条数
if(!root) return 0;
int tmp = 0;
if(root -> val == sum) tmp++;
tmp += rootpathSum(root -> left, sum - root -> val);
tmp += rootpathSum(root -> right, sum - root -> val);
return tmp;
}
void sum_rootpathSum(TreeNode* root, int sum){ // 遍历一遍树,将所有的rootpathSum相加
if(!root) return;
res += rootpathSum(root, sum);
sum_rootpathSum(root -> left, sum);
sum_rootpathSum(root -> right, sum);
}
public:
int pathSum(TreeNode* root, int sum) {
sum_rootpathSum(root, sum);
return res;
}
};
```
## 思路二
``` C++
class Solution {
private:
int res = 0;
void helper(TreeNode* node, int sum, int curSum, vector<TreeNode*>& out) {
if (!node) return;
curSum += node->val;
out.push_back(node);
if (curSum == sum) ++res;
int t = curSum;
for (int i = 0; i < out.size() - 1; i++) { // 循环判断以node为结束的子路径中是否有满足条件的
t -= out[i]->val;
if (t == sum) res++;
}
helper(node->left, sum, curSum, out);
helper(node->right, sum, curSum, out);
out.pop_back();
}
public:
int pathSum(TreeNode* root, int sum) {
vector<TreeNode*> out;
helper(root, sum, 0, out);
return res;
}
};
```
## 思路三
``` C++
class Solution {
private:
int helper(TreeNode* node, int sum, int curSum, unordered_map<int, int>& mp) {
if (!node) return 0;
curSum += node->val;
int res = mp[curSum - sum]; // 以node为结束的路径和等于sum的路径条数
mp[curSum]++; // mp记录root到(当前)所有节点的路径和的个数
res += helper(node->left, sum, curSum, mp) + helper(node->right, sum, curSum, mp);
mp[curSum]--;
return res;
}
public:
int pathSum(TreeNode* root, int sum) {
unordered_map<int, int>mp;
mp[0] = 1;
return helper(root, sum, 0, mp);
}
};
```
@@ -0,0 +1,77 @@
# [438. Find All Anagrams in a String](https://leetcode.com/problems/find-all-anagrams-in-a-string/description/)
# 思路
## 思路一
用固定长度(p.size)的滑动窗口在s上滑动,每次判断是否满足题意即可。
用一个数组p_count记录每个字符出现的次数,对p中出现的字符进行累加,对窗口中出现的字符进行累减,若这个数组的元素全为0,则满足题意。
时间复杂度O(n), 空间复杂度O(1)
每次判断p_count是否全为0时最坏都要遍历整个数组,比较费时间。思路二将解决这个问题
## 思路二*
和思路一类似,先用一个数组p_count记录p中各字符出现的次数。然后,初始化一个长度为0的窗口,low = high = 0。
第一步先扩展窗口,也就是在右边界high上做文章。每次high读到s的一个字符char,当p_count[char]的值大于0时,很明显就是窗口中进入了一个p中含有的字符。
我们可以取一个变量char_num值初始为p中所有字符的总数。每次有一个p中字符从high进入窗口就char_num-–, 每次有p的字符从窗口low出去就char_num++。
这样,当char_num == 0的时候,表明我们的窗口中包含了p中的全部字符,得到一个结果。
# C++
## 思路一
```
class Solution {
private:
bool isOK(vector<int>count){
for(int num: count)
if(num != 0) return false;
return true;
}
public:
vector<int> findAnagrams(string s, string p) {
vector<int>p_count(26, 0), res;
if(p.size() > s.size()) return res;
for(int i = 0; i < p.size(); i++){ // 处理第一个窗口
p_count[p[i] - 'a']++;
p_count[s[i] - 'a']--;
}
int pos = 0;
while(1){ // 窗口不断移动
if(isOK(p_count)) res.push_back(pos);
pos++;
if(pos + p.size() > s.size()) break;
p_count[s[pos - 1] - 'a']++;
p_count[s[pos + p.size() - 1] - 'a']--;
}
return res;
}
};
```
## 思路二
```
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int>p_count(26, 0), res;
int char_num = p.size(); // char_num代表在p在窗口中的字符数
if(char_num > s.size()) return res;
for(int i = 0; i < p.size(); i++) p_count[p[i] - 'a']++; // p_count记录p中出现的字母次数
int low = 0, high = 0;
// 这个for循环完全可以不加,因为后面的while循环会完成同样的工作,不过加上后由于少执行了一个if语句所以会快一些
for(; high < p.size() - 1; high++){ // 窗口初始化成p.size-1大小(因为while里面会立马增大窗口大小)
if(p_count[s[high] - 'a'] > 0) char_num--;
p_count[s[high] - 'a']--;
}
while(high < s.size()){
if(p_count[s[high] - 'a'] > 0) char_num--; // 窗口右界右移
p_count[s[high] - 'a']--;
high++;
if(char_num == 0) res.push_back(low); // char_num 等于0 代表p中字符全在窗口中
if(high - low == p.size()){ // 窗口超过长度限制,左界右移
if(p_count[s[low] - 'a'] >= 0) char_num++;
p_count[s[low] - 'a']++;
low++;
}
}
return res;
}
};
```
+39
View File
@@ -0,0 +1,39 @@
# [441. Arranging Coins](https://leetcode.com/problems/arranging-coins/description/)
# 思路
## 思路一
可以考虑用n不断减去1、2、3...直到不能再减,这样最后减去的那个数就是所求。
## 思路二
题目就是求满足k(k+1)/2 <= n的最大的k,即 k(k+1) <= 2n <= (k+1)(k+2),所以k <= (int)sqrt(2n) <= k+1
所以先判断(int)sqrt(2n)是否满足,若满足返回即可,否则返回(int)sqrt(2n) - 1。
## 思路三
在寻找满足k(k+1)/2 <= n的最大的k时也可以用二分搜索,代码略。
# C++
## 思路一
```
class Solution {
public:
int arrangeCoins(int n) {
if(n <= 1) return n;
int res = 1;
while(n >= res){
n -= res;
res++;
}
return res - 1;
}
};
```
## 思路二
```
class Solution {
public:
int arrangeCoins(int n) {
if(n <= 1) return n;
int res = sqrt(2 * (double)n);
long long i = res;
if(i * (i + 1) / 2 <= n) return (int)i;
return (int)(i - 1);
}
};
```
+39
View File
@@ -0,0 +1,39 @@
# [443. String Compression](https://leetcode.com/problems/string-compression/description/)
# 思路
根据题意压缩字符串。
用res记录当前处理过的字符串压缩后的长度,num记录当前字符出现了多少次。
# C++
```
class Solution {
public:
int compress(vector<char>& chars) {
int res = 0, tmp, num = 1; // num初始为1
for(int i = 1; i < chars.size(); i++){
if(chars[i] == chars[i - 1]) num++;
else{
chars[res++] = chars[i - num];
if(num == 1) continue; // 若出现次数仅为1则后面不加“1”
// 以下5行将int型转为char型,也可用string s = to_string(num)转,不过好像慢一些
tmp = res;
while(num != 0){
chars[res++] = (num % 10 + '0');
num /= 10;
}
reverse(chars.begin() + tmp, chars.begin() + res);
num = 1;
}
}
chars[res++] = chars[chars.size() - 1];
if(num > 1){
tmp = res;
while(num != 0){
chars[res++] = (num % 10 + '0');
num /= 10;
}
reverse(chars.begin() + tmp, chars.begin() + res);
}
return res;
}
};
```
+31
View File
@@ -0,0 +1,31 @@
# [447. Number of Boomerangs](https://leetcode.com/problems/number-of-boomerangs/description/)
# 思路
这道题定义了一种类似回旋镖形状的三元组结构,要求第一个点和第二个点之间的距离跟第一个点和第三个点之间的距离相等。
现在给了我们n个点,让我们找出回旋镖的个数。那么我们想,如果我们有一个点a,还有两个点b和c,如果ab和ac之间的距离相等,那么就有两种排列方法abc和acb;
如果有三个点b,c,d都分别和a之间的距离相等,那么有六种排列方法,abc, acb, acd, adc, abd, adb,推广一下,如果有n个点和a距离相等,那么排列方式为n(n-1),这属于最简单的排列组合问题了。
那么我们问题就变成了遍历所有点,让每个点都做一次点a,然后遍历其他所有点,统计和a距离相等的点有多少个,然后分别带入n(n-1)计算结果并累加到res中。
时间复杂度O(n^2)
# C++
```
class Solution {
public:
int numberOfBoomerangs(vector<pair<int, int>>& points) {
unordered_map<int, int>dist_count;
int delta_x, delta_y, dist_2, res = 0;
unordered_map<int, int>::iterator it;
for(int i = 0; i < points.size(); i++){
for(int j = 0; j < points.size(); j++){
delta_x = points[i].first - points[j].first;
delta_y = points[i].second - points[j].second;
dist_2 = delta_x * delta_x + delta_y * delta_y;
dist_count[dist_2]++;
}
for(it = dist_count.begin(); it != dist_count.end(); it++)
res += (it->second * (it -> second - 1));
dist_count.clear();
}
return res;
}
};
```
@@ -0,0 +1,68 @@
# [448. Find All Numbers Disappeared in an Array](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/description/)
# 思路
题目要求线性的时间复杂度且不适用额外的空间,所以最基本的先排序再判断就不行了。
## 思路一
依然是考虑将数组排序。由于数组元素的特殊性,其实每一个数在排序后应该在的位置是能立即确定的,例如2应该在nums[1], 5应该在nums[4]。
所以我们从前往后遍历数组,不断循环将元素交换到应该在的位置,直到无法交换。
无法交换的情况有二:
* nums[i] == i+1, 即已经在应该在的位置;
* nums[nums[i]-1] == nums[i], 即元素值等于期望交换的位置的元素值
仔细观察其实可以发现情况2包含了情况1, 所以代码中只写情况2就行了。
如果满足交换条件,则每次都会使一个元素处在正确位置,因为总共有n个元素,所以至多需要n-1次交换, 所以时间复杂度O(n)。
## 思路二
我们只需要知道没有出现哪些元素,即第一次遍历的时候对已经出现的元素作个二值标记,第二次遍历时候寻找没有标记的位置即可。
怎么进行标记呢?容易想到的就是开辟一个大小为n的标记数组(或者用map),但是有没有不用额外空间的办法呢?
由于nums就是一个大小为n的数组,所以我们考虑在不覆盖掉原有的元素(即第二次遍历时能够得到该位置原元素值)的基础上用nums作为标记数组。
为了第二次遍历时能够得到该位置原元素值,有多种思路:
* 由于元素都是1~n的,所以如果对某元素加上n+1, 第二次遍历时对n+1取模即可得到原元素;
* 由于元素都是大于0的,所以使某元素为对应的负数也能达到目的。
因为加上n+1可能存在溢出的问题,所以我们采取第二种思路,即从前往后遍历,
将位置为nums[i]-1的元素变为对应的负数(即nums[abs(nums[i])-1] = -abs(nums[abs(nums[i])-1]))。
第二次遍历时,若位置i上的数为正数,则缺失了数i+1。
两次遍历,所以时间复杂度O(n)
# C++
## 思路一
```
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
int tmp, i = 0;
while( i < nums.size()){
if(nums[nums[i]-1] == nums[i]){ // 无法交换
i++;
continue;
}
// 交换
tmp = nums[nums[i]-1];
nums[nums[i]-1] = nums[i];
nums[i] = tmp;
}
vector<int>result;
for(int i = 0; i < nums.size(); i++)
if(nums[i] != (i + 1))
result.push_back(i+1);
return result;
}
};
```
## 思路二
```
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
for(int i = 0; i < nums.size(); i++)
nums[abs(nums[i]) - 1] = -1 * abs(nums[abs(nums[i]) - 1]);
vector<int>result;
for(int i = 0; i < nums.size(); i++)
if(nums[i] > 0)
result.push_back(i+1);
return result;
}
};
```
@@ -0,0 +1,17 @@
# [453. Minimum Moves to Equal Array Elements](https://leetcode.com/problems/minimum-moves-to-equal-array-elements/description/)
# 思路
这题咋一看很吓人,因为每一步必须改变n-1个数而不是一个数,但是仔细分析发现由于最终的目标只是一个相对的(即所有元素相等)关系,所以将n-1个元素增加1与将1个元素减去1没有任何区别。
所以这题就转换成每次将一个元素减1,需要进行多少次才能让所有元素相等。由于只能减,所以最终元素都应该变成原数组的最小值,而所求次数则是所有元素与最小值的差的和。
时间复杂度O(n),空间复杂度O(1)
# C++
```
class Solution {
public:
int minMoves(vector<int>& nums) {
int min_num = nums[0], res = 0;
for(int num: nums) min_num = min(num, min_num);
for(int num: nums) res += (num - min_num);
return res;
}
};
```
+25
View File
@@ -0,0 +1,25 @@
# [455. Assign Cookies](https://leetcode.com/problems/assign-cookies/description/)
# 思路
贪心。从最小饼干开始分配直到无法分配。
先将g和s从小到大排序,孩子和饼干分别用下标i和j,i和j都从0开始:
* 若g[i] > s[j],即此时的最小饼干j无法满足此时要求最少的孩子i,应该尝试下一个饼干j+1;
* 若g[i] <= s[j],即此时的最小饼干能满足此时要求最少的孩子i,应该将j分配给i。i++以处理下一个孩子,j++以处理下一块饼干。
时间复杂度O(n)。
# C++
```
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int i = 0, j = 0, res = 0;
while(i < g.size() && j < s.size()){
while(j < s.size() && g[i] > s[j]) j++;
if(j++ < s.size()) res++;
i++;
}
return res;
}
};
```

Some files were not shown because too many files have changed in this diff Show More