Leetcode题库(41-60)

Leetcode41. 缺失的第一个正数

思路: 要求找到缺失的最小整数。因为最大长度为int(nums.size()),所以对于小于等于0以及大于这个最大长度的值,我们可以把他们全部设为int(nums.size())+1,作为一个无效数值。
然后对于每个有效数值 n u m [ i ] num[i] num[i]而言,数组中总有一个下标 j = n u m [ i ] − 1 j=num[i]-1 j=num[i]1,那我们不妨把每个数字映射到这个下标所对应的数位上来。由于在前面,我们把所有的值都置为了整数,那我们可以将有映射值的下标 j j j上的数字 n u m [ j ] num[j] num[j]置为负数,表示数组中有等于 j + 1 j+1 j+1的数字。
最后查看第一个不为负数的下标,将其输出。
代码:

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
    	// 最大答案
        int mx = int(nums.size()) + 1, tmp;
        // 不合法的值置为最大答案
        for(int & num : nums) if(num <= 0 || num > mx) num = mx;
        // 遍历每个数
        for(int & num : nums){
        	// tmp为当前数字(正数形式)
            tmp = num < 0 ? -num : num;
            // 如果合法且存在, 那么更改对应下标的数字
            if(tmp < mx && nums[tmp - 1] > 0) nums[tmp - 1] *= -1;
        }
        // 查找第一个不满足的位置
        for(int i = 0;i < nums.size();i++) if(nums[i] > 0) return i + 1;
        return mx;
    }
};



Leetcode42. 接雨水

思路: 前缀数组+后缀数组。对于每个位置而言,当前位置能接的水量取决于在这个位置左侧最高挡板高度,以及这个位置右侧最高挡板高的的最小值(短板效应)。那我们的目的就是找到这两个最大值,那就用一个前缀数组维护从头到第 i i i个位置的最大值,用一个后缀数组 a f t [ i ] aft[i] aft[i]维护从尾部开始到头部的最大值,那么对于每个位置而言,这两个数求最小值就是对于当前位置的短板高度,而这个高度与当前位置高度差值就是能存水的值。
由于前缀仅需简单处理下最大值,这里就直接用单个数存储到当前位置的最大值。
代码:

class Solution {
public:
	// 存放后缀最大值
    int aft[20004];
    int trap(vector<int>& height) {
    	// 初始化从后到第i位的最大值
        for(int i = height.size() - 1;i >= 0;i--) aft[i] = max(aft[i + 1], height[i]);
        // 答案, 前缀最大值
        int ans = 0, pre = 0;
        for(int i = 0;i < height.size();i++){
        	//	更新前缀最大值
            pre = max(pre, height[i]);
            ans += min(pre, aft[i]) - height[i];
        }
        return ans;
    }
};



Leetcode43. 字符串相乘

思路: 模拟一个大数乘法。我们小学学过竖式乘法,那我们就是要模拟这个情况。由于数字正常情况下,从左到右是从高位到低位,那我们为了计算方便,我们将其翻转,使其从低位到高位。然后每次取num2最低位,从num1最低位开始一个一个乘过去,处理一下进位问题。
代码:

class Solution {
public:
    string multiply(string num1, string num2) {
    	// 翻转,使其从低位到高位
        std::reverse(num1.begin(), num1.end());
        std::reverse(num2.begin(), num2.end());
        // 记录答案
        string ans = "";
        for(int i = 0;i < num2.length();i++){
            int tmp = 0;
            for(int j = 0;j < num1.length();j++){
            	// 如果ans长度不够, 那往高位加0
                if(ans.length() <= i + j) ans = ans + "0";
                // 计算到当前位置的值, 值为进位值+乘法所得积+当前位置本来的值
                tmp += (num2[i] - '0') * (num1[j] - '0') + ans[i + j] - '0';
                // 获得当前位置变化成的值
                ans[i + j] = (char)(tmp % 10 + '0');
                // 获得进位值
                tmp /= 10;
            }
            // 处理最后有进位值没加入答案的情况
            if(tmp){
                if(ans.length() <= i + num1.length()) ans = ans + "0";
                ans[i + num1.length()] = (char)(tmp + '0');
            }
        }
        std::reverse(ans.begin(), ans.end());
        // 去除前导零
        int l = 0;
        while(l < ans.length() && ans[l] == '0') ++l;
        // 全0的时候保留一个0
        l = min(l, (int)ans.length() - 1);
        return ans.substr(l, ans.length() - l);
    }
};



Leetcode44. 通配符匹配

思路: dp。
我们设置一个dp数组,dp[i][j]代表s.substr(0,i)p.substr(0,j)匹配情况。然后对于每种s[i]和p[j]匹配情况分类讨论。

  1. s[i]==p[j]
    s中当前位置的字符与p中当前位置字符相同,相当于在s中不存在s[i],在p中不存在p[j]的情况,则dp[i][j]=dp[i-1][j-1]
  2. p[j]=='?'
    当前情况相当于上一种情况,即dp[i][j]=dp[i-1][j-1]
  3. p[j]=='*'
    3.1 当前位置在s中应匹配的字符为空,则当前匹配情况与不存在p[j]的时候相同,则dp[i][j]=dp[i][j-1]
    3.2 匹配当前字符,相当于在s中不存在s[i],在p中不存在p[j]的情况,则dp[i][j]=dp[i-1][j-1]
    3.3 当前一个'*'匹配多个字符的情况,相当于s中不存在s[i]的情况,则dp[i][j]=dp[i-1][j]

需要特别判断当p字符串开头为‘*’的情况。对于p开头为‘*’的时候,两字符串匹配情况相当于删除任意个‘*’,所以当p[i - 1] == '*'的时候dp[0][i] = true
代码:

class Solution {
public:
    bool dp[2004][2004];
    bool isMatch(string s, string p) {
    	// 两字符串都为空的时候为匹配
        dp[0][0] = true;
        // 处理p开头为*的情况
        for(int i = 1;i <= p.length() && p[i - 1] == '*';i++) dp[0][i] = true;
        for(int i = 0;i < s.length();i++){
            for(int j = 0;j < p.length();j++){
            	// 两字符匹配与当前p为?的情况相同
                if(s[i] == p[j] || p[j] == '?') dp[i + 1][j + 1] |= dp[i][j];
                // 当前p为* 包含三种情况
                else if(p[j] == '*') dp[i + 1][j + 1] |= dp[i][j] | dp[i + 1][j] | dp[i][j + 1];
            }
        }
        return dp[s.length()][p.length()];
    }
};



Leetcode45. 跳跃游戏 II

思路: 对于每一次移动而言,都有一段可以移动到的新区间,我们将这个新区间用[l,r]表示,那么在下一次移动中,新的区间为 [ m a x ( m i n ( i + n u m s [ i ] ) , r + 1 ) , m a x ( i + n u m s [ i ] ) ] , l ≤ i ≤ r [max(min(i+nums[i]), r+1),max(i+nums[i])], l \le i \le r [max(min(i+nums[i]),r+1),max(i+nums[i])],lir。那我们只要每次维护他的lr,在新移动的时候维护新的左右边界即可。时间复杂度 O ( N ) O(N) O(N)
代码:

class Solution {
public:
    int jump(vector<int>& nums) {
    	// l区间左指针 r区间右指针 ans答案
        int l = 0,r = 0, ans = 0, tmp;
        while(r < nums.size() - 1){
            tmp = r;
            // 维护右指针新位置
            for(int i = l;i <= r;i++) tmp = max(tmp, nums[i] + i);
            // 更新左右指针
            l = r + 1, r = tmp;
            ++ans;
        }
        return ans;
    }
};



Leetcode46. 全排列

思路: C++ STL中有个函数next_permutation(),他可以使容器内的值变成其下一个全排列并返回true,否则返回false
代码:

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> ans;
        // 先排序, 将当前容器内的值成为最小全排列
        sort(nums.begin(), nums.end());
        do{
            ans.push_back(nums);
        }while(next_permutation(nums.begin(), nums.end()));
        return ans;
    }
};



Leetcode47. 全排列 II

思路: 与上一题相同,因为next_permutation(),他可以使容器内的值变成其下一个全排列,下一个全排列严格大于上一个,所以代码相同。
代码:

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> ans;
        sort(nums.begin(), nums.end());
        do{
            ans.push_back(nums);
        }while(next_permutation(nums.begin(), nums.end()));
        return ans;
    }
};



Leetcode48. 旋转图像

思路: 顺时针旋转 90 度相当于先水平翻转,然后沿副对角线翻转。
代码:

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size() - 1;
        // 水平翻转
        for(int i = 0;i <= n;i++){
            for(int j = 0;(j << 1) < n;j++) swap(matrix[i][j], matrix[i][n - j]);
        }
        // 沿副对角线翻转
        for(int i = 0;i <= n;i++){
            for(int j = 0;j < n - i;j++) swap(matrix[i][j], matrix[n - j][n - i]);
        }
    }
};



Leetcode49. 字母异位词分组

思路: 哈希。对于每个字符串分组而言,根据他们所包含的每个字符个数分组。所以我们只需要将每个字符串26个字母分别多少个给拆分出来就能得到这个字符串的特征值。对于特征值相同性判断而言,我们可以将这个特征值进行哈希(就是把他看成一个26位的,HS进制的数字),哈希值相同的就是相同特征值的字符串。
代码:

class Solution {
public:
    long long HS = 1331, mod = 1000000007;
    int times[26], ans, cnt = 0;
    map<int,int> mp;
    // 存储答案
    vector<vector<string>> result;
    // 空Vector
    vector<string> newVt;
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        for(int i = 0;i < strs.size();i++){
        	// 初始化每个字符出现次数
            memset(times, 0, sizeof(times));
            ans = 0;
            // 计算每个字符出现次数
            for(int j = 0;j < strs[i].length();j++) times[strs[i][j] - 'a']++;
            // 计算出现次数哈希值
            for(int j = 0;j < 26;j++) ans = (ans * HS % mod + times[j]) % mod;
            // 如果没出现过这个哈希值, 那就新开一类
            if(mp[ans] == 0) mp[ans] = ++cnt, result.push_back(newVt);
            // 将字符串放到对应类别中
            result[mp[ans] - 1].push_back(strs[i]);
        }
        return result;
    }
};



Leetcode50. Pow(x, n)

思路: 浮点数快速幂。要判断阶数为负数、初始为0的情况。将n转为long long,避免在计算过程中溢出。
代码:

class Solution {
public:
    double myPow(double x, int n) {
        if(x == 0) return 0;
        double ans = 1;
        int flag = 0;
        long long m = n;
        // 判断阶数为负的情况
        if(m < 0) flag = 1, m = -m;
        // 正常快速幂
        while(m){
            if(m & 1) ans = ans * x;
            x *= x;
            m >>= 1;
        }
        if(flag) ans = 1.0 / ans;
        return ans;
    }
};



Leetcode51. N 皇后

思路: dfs。对于每一行/列/主对角线/副对角线而言,必定有且只有一个Q。那我们只需要维护每一行/列/主对角线/副对角线的Q的个数,然后枚举每一行的Q的位置,并判断是否合法即可。
代码:

class Solution {
public:
    vector<string> ans;
    vector<vector<string>> result;
    // 每一行/列/主对角线/副对角线是否有Q, 有为1
    int row[10], col[10], dia1[19], dia2[19];
    void dfs(int x,int n){
    	// 如果已经使每行有合法情况, 则把当前布局存储到答案
        if(x >= n){
            result.push_back(ans);
            return;
        }
        for(int i = 0;i < n;i++){
        	// 判断当前位置是否合法
            if(row[x] || col[i] || dia1[n-1-x+i] || dia2[x+i]) continue;
            // 合法的时候将当前位置放置Q
            ans[x][i] = 'Q';
            // 更新所在行/列/主对角线/副对角线存在Q的情况
            row[x] = col[i] = dia1[n-1-x+i] = dia2[x+i] = 1;
            // 查找下一行
            dfs(x + 1, n);
            // 回溯
            ans[x][i] = '.';
            row[x] = col[i] = dia1[n-1-x+i] = dia2[x+i] = 0;
        }
    }
    vector<vector<string>> solveNQueens(int n) {
        string s = "";
        // 初始化棋盘
        for(int i = 0;i < n;i++) s = s + '.';
        for(int i = 0;i < n;i++) ans.push_back(s);
        dfs(0, n);
        return result;
    }
};



Leetcode52. N 皇后 II

思路: 与上一题一样,将存储答案的步骤改为result++即可。
代码:

class Solution {
public:
    int row[10], col[10], dia1[19], dia2[19], result;
    vector<string> ans;
    void dfs(int x,int n){
    	// 如果已经使每行有合法情况, 则把当前布局存储到答案
        if(x >= n){
            ++result;
            return;
        }
        for(int i = 0;i < n;i++){
        	// 判断当前位置是否合法
            if(row[x] || col[i] || dia1[n-1-x+i] || dia2[x+i]) continue;
            // 合法的时候将当前位置放置Q
            ans[x][i] = 'Q';
            // 更新所在行/列/主对角线/副对角线存在Q的情况
            row[x] = col[i] = dia1[n-1-x+i] = dia2[x+i] = 1;
            // 查找下一行
            dfs(x + 1, n);
            // 回溯
            ans[x][i] = '.';
            row[x] = col[i] = dia1[n-1-x+i] = dia2[x+i] = 0;
        }
    }
    int totalNQueens(int n) {
        string s = "";
        // 初始化棋盘
        for(int i = 0;i < n;i++) s = s + '.';
        for(int i = 0;i < n;i++) ans.push_back(s);
        dfs(0, n);
        return result;
    }
};



Leetcode53. 最大子数组和

思路:
代码:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int sum = 0, mn = 0, ans = -1e9;
        for(int i = 0;i < nums.size();i++){
            sum += nums[i];
            ans = max(ans, sum - mn);
            mn = min(mn, sum);
        }
        return ans;
    }
};



Leetcode54. 螺旋矩阵

思路: 一共四种前进方式,右下左上,每次运行的区域取决于已经走过的圈数。就只需要模拟当前前进情况即可。
代码:

class Solution {
public:
	// 顺时针顺序, 右下左上
    int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> ans;
        // di: 走哪个方向  circle: 第几圈
        int di = 0, x = 0, y = 0, circle = 0, mxsize = matrix.size() * matrix[0].size(), row = matrix.size(), col = matrix[0].size();
        for(int i = 0;i < mxsize;i++){
        	// 把当前答案存入
            ans.push_back(matrix[x][y]);
            // 判断各个方向碰到边界的条件
            if(di == 0 && y + circle == col - 1) di++;
            else if(di == 1 && x + circle == row - 1) di++;
            else if(di == 2 && y == circle) di++;
            // 如果是向上碰到边界还需要加当前圈数
            else if(di == 3 && x == circle + 1) di = 0, circle++;
            x += dir[di][0], y += dir[di][1];
        }
        return ans;
    }
};



Leetcode55. 跳跃游戏

思路: 与45题一样,只不过多了nums[i]==0的情况。
代码:

class Solution {
public:
    bool canJump(vector<int>& nums) {
    	// l区间左指针 r区间右指针
        int l = 0,r = 0, tmp;
        // 多判一个当前左区间指针在右区间指针右侧的情况
        while(r < nums.size() && l <= r){
            tmp = r;
            for(int i = l;i <= r;i++) tmp = max(tmp, nums[i] + i);
            l = r + 1, r = tmp;
        }
        // 如果右指针没碰到右边界, 说明到不了
        if(r < nums.size() - 1) return false;
        else return true;
    }
};



Leetcode56. 合并区间

思路: 将各个区间按左区间从小到大排序。然后从左到右遍历所有区间。一共三种情况:

  1. 答案区间右指针处于遍历到的区间的左右指针区间,那么可以将两个区间合并
  2. 答案区间右指针大于遍历到的区间的右指针,那么忽略当前遍历到的区间
  3. 答案区间右指针小于遍历到的区间的左指针,那么将答案区间添加到答案,将遍历到的区间作为新的答案区间
    代码:
class Solution {
public:
    static bool cmpInMerge(vector<int> a, vector<int> b){
        return a[0] < b[0];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
    	// 排序
        sort(intervals.begin(), intervals.end(), cmpInMerge);
        vector<vector<int>> ans;
        vector<int> tmp = intervals[0];
        for(int i = 0;i < intervals.size();i++){
        	// 答案区间右指针处于遍历到的区间的左右指针区间
            if(tmp[1] >= intervals[i][0] && tmp[1] <= intervals[i][1]) tmp[1] = intervals[i][1];
            // 答案区间右指针小于遍历到的区间的左指针
            else if(tmp[1] < intervals[i][0]) ans.push_back(tmp), tmp = intervals[i];
        }
        ans.push_back(tmp);
        return ans;
    }
};



Leetcode57. 插入区间

思路: 与上一题一样,将newInterval插入intervals就行。在for里面每次将newInterval与答案区间比较可以将 O ( N l o g N ) O(NlogN) O(NlogN)复杂度降到 O ( N ) O(N) O(N)(但我懒,没写)。
代码:

class Solution {
public:
    static bool cmpInInsert(vector<int> a, vector<int> b){
        return a[0] < b[0];
    }
    vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
        intervals.push_back(newInterval);
        sort(intervals.begin(), intervals.end(), cmpInInsert);
        vector<vector<int>> ans;
        vector<int> tmp = intervals[0];
        for(int i = 0;i < intervals.size();i++){
            if(tmp[1] >= intervals[i][0] && tmp[1] <= intervals[i][1]) tmp[1] = intervals[i][1];
            else if(tmp[1] < intervals[i][0]) ans.push_back(tmp), tmp = intervals[i];
        }
        ans.push_back(tmp);
        return ans;
    }
};



Leetcode58. 最后一个单词的长度

思路: 先把结尾空格删完,然后统计出现空格前单词长度。
代码:

class Solution {
public:
    int lengthOfLastWord(string s) {
        int ans = 0, r = s.length() - 1;
        while(r >= 0 && s[r] == ' ') --r;
        while(r >= 0 && s[r] != ' ') --r, ++ans;
        return ans;
    }
};



Leetcode59. 螺旋矩阵 II

思路: 与54题一样,将54中输入数组用vector<vector<int>> ans替代。每次将当前序号添加到ans中。
代码:

class Solution {
public:
    int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> ans;
        vector<int> tmp;
        for(int i = 0;i < n;i++) tmp.push_back(0);
        for(int i = 0;i < n;i++) ans.push_back(tmp);
        int di = 0, x = 0, y = 0, circle = 0, mxsize = n * n, row = n, col = n;
        for(int i = 0;i < mxsize;i++){
            ans[x][y] = i + 1;
            if(di == 0 && y + circle == col - 1) di++;
            else if(di == 1 && x + circle == row - 1) di++;
            else if(di == 2 && y == circle) di++;
            else if(di == 3 && x == circle + 1) di = 0, circle++;
            x += dir[di][0], y += dir[di][1];
        }
        return ans;
    }
};



Leetcode60. 排列序列

思路: 对于固定长度 p p p而言,排列个数为 p ! p! p!。那么对于长度为 n + 1 n+1 n+1的数列而言,如果为第 k k k个排列,那么最前一个数可以用 k / ( n ! ) k/(n!) k/(n!)算出。以此类推。
代码:

class Solution {
public:
    string getPermutation(int n, int k) {
    	// fact:阶乘  vis:标记是否访问
        int fact[10] = {1}, vis[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, l, r;
        string ans = "";
        for(int i = 1;i < 10;i++) fact[i] = fact[i - 1] * i;
        --k;  // 相当于用了进制的思想
        for(int i = 1;i <= n;i++){
            l = k / fact[n - i];  // 算出最前一个数字是未访问数字中的第几个
            k -= l * fact[n - i];  // 删除开头的影响
            r = 0;  // 寻找答案
            while(true){
                if(vis[r]) ++r;
                else if(l > 0) --l, ++r;
                else{
                    vis[r] = 1;
                    break;
                }
            }
            ans = ans + (char)(r + '1');
        }
        return ans;
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值