Leetcode 每日一题·4月
1006 笨阶乘
模拟法,四个一组,末尾部分特殊处理。
class Solution {
public:
int clumsy(int N) {
if(N <= 2) return N;
if(N == 3) return 6;
int sum = N * (N-1) / (N-2) + N-3;
N-=4;
while(N >= 4)
{
sum += (- N * (N-1) / (N-2) + N-3);
N-=4;
}
return sum - clumsy(N);
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
————
17/21 直方图的水量
双指针,记录从左到右和从右到左的最大值,再遍历计算面积得到结果
class Solution {
public:
int trap(vector<int>& height) {
if(height.empty()) return 0;
int n = height.size();
vector<int> left(n), right(n);
left[0] = height[0];
right[n - 1] = height[n - 1];
for(int i = 1; i < n; i++ )
left[i] = max(left[i - 1], height[i]);
for(int i = n - 2; i >= 0; i-- )
right[i] = max(right[i + 1], height[i]);
int res = 0;
for(int i = 0; i < n; i++ )
res += min(left[i], right[i]) - height[i];
return res;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
————
1143 最长公共子序列
动态规划,模板题
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int len1 = text1.size(), len2 = text2.size();
vector<vector<int>> dp(len1+1,vector<int>(len2+1,0));
for(int i = 1; i <= len1; i++ )
{
for(int j = 1; j <= len2; j++ )
{
if(text1[i-1] == text2[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
return dp[len1][len2];
}
};
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
————
781 森林中的兔子
根据题意,若答案出现 a 次 x 只,则该颜色至少有2*(x+1)只,即需要有(a-1) / (x + 1) + 1) * (x + 1)只兔子。
例:5出现了6次,则正好不缺,每只都看到另外5只同色;若出现了3次,则需要6只兔子(补成一组);若出现次数多余一组,同理把不足一组的部分补成一组。
我用了map保存颜色和对应数量,方便统计。
class Solution {
public:
int numRabbits(vector<int>& answers) {
if(answers.empty()) return 0;
map<int, int> mp;
int res = 0;
for(int i : answers)
++mp[i];
for(auto i : mp)
res += ((i.second - 1) / (i.first + 1) + 1) * (i.first + 1);
return res;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
————
88 合并两个有序数组
注意应该用尾插法防止数据覆盖
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int len = m + n - 1;
int i = m - 1, j = n - 1;
while (i >= 0 && j >= 0) {
if (nums1[i] >= nums2[j]) nums1[len --] = nums1[i --];
else nums1[len --] = nums2[j --];
}
while (i >= 0) nums1[len --] = nums1[i --];
while (j >= 0) nums1[len --] = nums2[j --];
return ;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
————
80 删除有序数组中的重复项 II
原地算法,模板题
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size() <= 2) return nums.size();
int index = 1;
for(int i = 2; i < nums.size(); i++)
if(nums[i] != nums[index - 1])
nums[++index] = nums[i];
return index + 1;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
————
81 搜索旋转排序数组 II
先确认旋转情况(判断头尾),然后缩短边界(去重)。
二分查找target,查找时也要注意缩短边界。
class Solution {
public:
bool search(vector<int>& nums, int target) {
int l = 0, r = nums.size()-1;
if(nums[0] >= nums.back())
{
if(target == nums.back())
return true;
else if(target < nums.back())
while(l < r && nums[l] <= nums[l+1]) ++l;
else if(target > nums.back())
while(l < r && nums[r] >= nums[r-1]) --r;
}
while(l <= r)
{
while(l < r && nums[l] == nums[l+1]) ++l;
while(l < r && nums[r] == nums[r-1]) --r;
int mid = (l + r) / 2;
if(nums[mid] == target)
return true;
else if(nums[mid] > target)
r = mid - 1;
else
l = mid + 1;
}
return false;
}
};
时间复杂度:
O
(
n
l
o
g
(
n
)
)
O(nlog(n))
O(nlog(n)),最坏情况为
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
————
153 寻找旋转排序数组中的最小值
直接二分查找即可。
class Solution {
public:
int findMin(vector<int>& nums) {
if(nums.size() == 1) return nums[0];
int l = 0, r = nums.size() - 1;
while(l < r)
{
int mid = (l + r) / 2;
if(nums[mid] < nums[r])
r = mid;
else
l = mid + 1;
}
return nums[l];
}
};
时间复杂度:
O
(
n
l
o
g
(
n
)
)
O(nlog(n))
O(nlog(n))
空间复杂度:
O
(
1
)
O(1)
O(1)
————
154 寻找旋转排序数组中的最小值 II
结合81和153,使用二分查找最小值,查找时注意缩短边界
class Solution {
public:
int findMin(vector<int>& nums) {
if(nums.size() == 1) return nums[0];
int l = 0, r = nums.size() - 1;
while(l < r)
{
while(l < r && nums[l] == nums[l+1]) l++;
while(l < r && nums[r] == nums[r-1]) r--;
if(l == r) return nums[l];
int mid = (l + r) / 2;
if(nums[mid] < nums[r])
r = mid;
else
l = mid + 1;
}
return nums[l];
}
};
时间复杂度:
O
(
n
l
o
g
(
n
)
)
O(nlog(n))
O(nlog(n))
空间复杂度:
O
(
1
)
O(1)
O(1)
————
263 丑数
递归
class Solution {
public:
bool isUgly(int n) {
if(n <= 0) return false;
if(n == 1) return true;
if(n % 2 == 0) return isUgly(n / 2);
if(n % 3 == 0) return isUgly(n / 3);
if(n % 5 == 0) return isUgly(n / 5);
return false;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
————
264 丑数 II
三指针,记录2,3,5在质因数中出现的次数,每次选择三个指针中最小的作为新的丑数,直到第n个。
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> res(n, 1);
int num[3] = {0};
int a,b,c;
for(int i = 1; i < n; i++)
{
a = res[num[0]] * 2;
b = res[num[1]] * 3;
c = res[num[2]] * 5;
int t = min(a, min(b, c));
if(t == a) num[0]++;
if(t == b) num[1]++;
if(t == c) num[2]++;
res[i] = t;
}
return res.back();
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
————
179 最大数
重写比较函数,让每个数字以字符串的形式比较。
class Solution {
public:
static bool cmp(int x, int y)
{
string a = to_string(x), b = to_string(y);
return a + b > b + a;
}
string largestNumber(vector<int>& nums) {
sort(nums.begin(), nums.end(), cmp);
if(nums[0] == 0) return "0";
string res;
for(auto t : nums)
res += to_string(t);
return res;
}
};
时间复杂度:
O
(
n
l
o
g
(
n
)
)
O(nlog(n))
O(nlog(n))
空间复杂度:
O
(
m
)
O(m)
O(m),m是字符串的长度
————
783 二叉搜索树节点最小距离
递归法,pre保存一个临时值
class Solution {
public:
int INF = 0x3f3f3f3f;
int res = INF, pre = -INF;
int minDiffInBST(TreeNode* root) {
if(root)
{
minDiffInBST(root->left);
res = min(res, root->val - pre);
pre = root->val;
minDiffInBST(root->right);
}
return res;
}
};
————
208 实现 Trie (前缀树)
前缀树标准流程,可能存在内存泄露,可以考虑用智能指针规避,或者手动释放。
class Trie {
private:
Trie *child[26];
bool isEnd;
public:
/** Initialize your data structure here. */
Trie() {
isEnd = false;
for(int i = 0; i < 26; i++)
child[i] = nullptr;
}
/** Inserts a word into the trie. */
void insert(string word) {
Trie *tmp = this;
for(char c : word)
{
if(!tmp->child[c - 'a'])
tmp->child[c - 'a'] = new Trie();
tmp = tmp->child[c - 'a'];
}
tmp->isEnd = true;
}
/** Returns if the word is in the trie. */
bool search(string word) {
Trie *tmp = this;
for(char c : word)
{
if(!tmp->child[c - 'a'])
return false;
tmp = tmp->child[c - 'a'];
}
return tmp->isEnd;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
Trie *tmp = this;
for(char c : prefix)
{
if(!tmp->child[c - 'a'])
return false;
tmp = tmp->child[c - 'a'];
}
return true;
}
};
————
213 打家劫舍 II
动态规划,将环状房屋分成两个队列:不拿第一个的情况和不拿最后一个的情况,需要注意的是不拿第一个的情况最后一个是可以不拿的。取这两个队列的最大值即可。
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() == 0) return 0;
if(nums.size() == 1) return nums[0];
return max(dprange(nums, 0, nums.size()-2), //不拿最后一个
dprange(nums, 1, nums.size()-1));//不拿第一个
}
int dprange(vector<int>& nums, int start, int end)
{
int temp = 0;//dp[i-1] 拿了前一个,不拿这个
int pre = 0;//dp[i-2] 前一个没拿,可以考虑这个
int dp_i = 0;
for(int i=start; i<=end; ++i)
{
dp_i = max(temp, pre+nums[i]);
pre = temp; //没拿这个(前一个没拿的前提下)
temp = dp_i;//当前最佳情况
}
return dp_i;
}
};
————
87 扰乱字符串
官方题解
————
220 存在重复元素 III
利用排序快速判断值区间,再分析下标是否满足条件
(虽然是暴力法,时空意外的还可以)
class Solution {
public:
struct Num
{
int num, index;
};
static bool cmp1(Num a, Num b)
{
return a.num < b.num;
}
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
int len = nums.size();
if(len < 2 || t < 0 || k < 0) return false;
struct Num num[len];
for(int i = 0; i < len; i++)
{
num[i].num = nums[i];
num[i].index = i;
}
sort(num, num + len, cmp1);
int l = 0, r = 1;
while(r <= len - 1)
{
if(abs((long)num[l].num - (long)num[r].num) <= t)
{
if(abs(num[l].index - num[r].index) <= k)
return true;
else
r++;
}
else
{
l++;
r = l + 1;
}
}
return false;
}
};
时间复杂度:
O
(
n
l
o
g
(
n
)
)
O(nlog(n))
O(nlog(n))
空间复杂度:
O
(
n
)
O(n)
O(n)
————
26 删除有序数组中的重复项
原题链接
原地算法,模板题
————
27 移除元素
原题链接
原地算法
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
if(nums.size() < 1) return 0;
int index = 0;
for(int i = 0; i < nums.size(); i++)
{
if(nums[i] != val)
nums[index++] = nums[i];
}
return index;
}
};
————
28 实现 strStr()
原题链接
虽然可以直接用STL,也可以手撕KMP实现
class Solution {
public:
int strStr(string haystack, string needle) {
if(needle.length() == 0) return 0;
else if(haystack.length() == 0) return -1;
int len_h = haystack.length(), len_n = needle.length();
int next[len_n];
next[0] = -1;
for(int i = 0, j = -1; i < len_n - 1;)
{
if(j == -1 || needle[j] == needle[i])
{
j++;
i++;
next[i] = j;
}
else
{
j = next[j];
}
}
int i = 0, j = 0;
while(i < len_h && j < len_n)
{
if(j == -1 || haystack[i] == needle[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if(j == len_n)
return i - j;
return -1;
}
};
————
91 解码方法
class Solution {
public:
int numDecodings(string s) {
if(s.size() == 0 || (s.size() == 1 && s[0] == '0')) return 0;
if(s.size() == 1) return 1;
int len = s.size();
int dp[2] = {1,1};// [n-2] & [n-1] ;at least return 1
for(int i = 0; i < len; i++)
{
int tmp = 0;// this case
if(s[i] != '0')// case: 1 + n :this one
{
tmp += dp[1];
}
if(i > 0 && (s[i-1] == '1' || (s[i-1] == '2' && s[i] <= '6')))// case:2 + n :with the former
{
tmp += dp[0];
}
dp[0] = dp[1];// [n-2] = [n-1]
dp[1] = tmp;// [n-1] = this one
}
return dp[1];
}
};
————
363 矩形区域不超过 K 的最大数值和
二维前缀和
推荐题解
————
368 最大整除子集
推荐题解
————
377 组合总和 Ⅳ
动态规划,背包题。
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target + 1, 0);//建表
dp[0] = 1;//装入0的方法有1个:什么都不加;也为了方便后续累加
for(int i = 1; i <= target; i++)//背包从装入1到装满
{
for(int j = 0; j < nums.size(); j++)//枚举nums里的每个数
{
//如果这个数是可装入的,并且装入这个数后的组合数未溢出
if(i >= nums[j] && dp[i - nums[j]] <= INT_MAX - dp[i])
{
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
}
};
————
897 递增顺序搜索树
原题链接
栈模拟递归过程,模板
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* increasingBST(TreeNode* root) {
stack<TreeNode*> st;
TreeNode *dummy = new TreeNode(), *res = dummy;
while(root != nullptr || !st.empty())
{
while(root != nullptr)
{
st.push(root);
root = root->left;
}
root = st.top();
st.pop();
TreeNode *tmp = new TreeNode();
dummy->right = tmp;
dummy = tmp;
dummy->val = root->val;
root = root->right;
}
return res->right;
}
};
————
1011. 在 D 天内送达包裹的能力
前缀和+二分,二分答案区间来缩小范围,找到运送包裹的最小时间
class Solution {
public:
bool check(int m, vector<int> &weights, int d)
{
int count = 1, sum = weights[0];
for(int i = 1; i < weights.size(); )
{
if(sum + weights[i] > m)
{
sum = 0;
count++;
}
else
{
sum += weights[i++];
}
}
return count <= d;
}
int shipWithinDays(vector<int>& weights, int D) {
int len = weights.size();
if(len == 1) return weights[0];
int m = 0, sum = 0;
for(auto &num : weights)
{
if(num > m) m = num;
sum += num;
}
int l = m, r = sum;
while(l < r)
{
m = l + r >> 1;
if(check(m, weights, D))
r = m;
else
l = m + 1;
}
return r;
}
};
————
938. 二叉搜索树的范围和
原题链接
同897,模板题
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int rangeSumBST(TreeNode* root, int low, int high) {
if(root == nullptr) return 0;
stack<TreeNode*> st;
int sum = 0;
while(root != nullptr || !st.empty())
{
while(root != nullptr)
{
st.push(root);
root = root->left;
}
root = st.top();
st.pop();
if(root->val >= low && root->val <= high)
sum += root->val;
root = root->right;
}
return sum;
}
};
————
633. 平方数之和
原题链接
二分法查找
class Solution {
public:
bool judgeSquareSum(int c) {
int l = 0, r = sqrt(c);
while(l <= r)
{
if(l*l < c - r*r)
{
l++;
}
else if(l*l > c - r*r)
{
r--;
}
else
{
return true;
}
}
return false;
}
};
————
403. 青蛙过河
原题链接
动态规划,因为青蛙跳跃的距离有三种,使用三状态dp。
class Solution {
public:
bool canCross(vector<int>& stones) {
int len = stones.size();
bool dp[len][len+1];
memset(dp, false, sizeof dp);
dp[0][1] = true;
for(int i = 1; i < len; i++)
{
bool flag = false;
for(int j = i - 1; j >= 0; j--)
{
int tmp = stones[i] - stones[j];
if(tmp > i) break;
if(dp[j][tmp])
{
dp[i][tmp - 1] = true;
dp[i][tmp] = true;
dp[i][tmp + 1] = true;
flag = true;
}
}
if(i == len - 1 && !flag)
return false;
}
return true;
}
};
————
137. 只出现一次的数字 II
位运算法,实际上是数字电路原理,一个数视为32为二进制,把所有数字异或起来,每三次1抵消为0。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int a = 0, b = 0;
for(auto num : nums)
{
b = (b ^ num) & ~a;
a = (a ^ num) & ~b;
}
return b;
}
};