LeetCode 51 ~ 60

LeetCode 51. N 皇后

因为每行每列每条对角线都只能有一个皇后,所以可以将各行的皇后映射到同一列上,枚举每列每条对角线是否有皇后。
接着分析,对角线上的皇后。
皇后坐标为(i,j)

  1. 正对角线映射到一个数组dg中,由于此时i = j + c1的c1可能为负数,所以需要加上一个偏移量保证c1为正数,用dg[i - j + n] 记录这条正对角线
  2. 反对角线映射到一个数组udg,此时i = - j + c2 ,c2必定为正数,用udg[ i + j ]记录这条反对角线
    在回溯的过程中只要满足!col[i] && !dg[u - i + n] && !udg[u + i]这个条件就表示当前的坐标能够容纳新的皇后,如此去递归枚举每个位置,找出所有合法的皇后排列即可
    在这里插入图片描述
class Solution {
public:

    int n;
    vector<bool> col, dg, udg;
    vector<vector<string>> res;
    vector<string> path;

    vector<vector<string>> solveNQueens(int _n) {
        n = _n;
        col = vector<bool>(n);
        dg = udg = vector<bool>(n * 2);

        path = vector<string>(n, string(n, '.'));

        dfs(0);
        return res;
    }

    void dfs(int u) {
        if(u == n) {
            res.push_back(path);
            return;
        }

        for(int i = 0; i < n; i ++ ) {
            if(!col[i] && !dg[u - i + n] && !udg[u + i]) {
                col[i] = dg[u - i + n] = udg[u + i] = true;
                path[u][i] = 'Q';
                dfs(u + 1);
                path[u][i] = '.';
                col[i] = dg[u - i + n] = udg[u + i] = false;
            }
        
        }
    }
};

LeetCode 52. N皇后 II

思路同LeetCode 51. N 皇后,返回 (int)res.size()即可

class Solution {
public:
    
    int n;
    vector<bool> col, dg, udg;
    vector<vector<string>> res;
    vector<string> path;

    int totalNQueens(int _n) {
        n = _n;
        col = vector<bool>(n);
        dg = udg = vector<bool>(n * 2);

        path = vector<string>(n, string(n, '.'));

        dfs(0);
        return (int)res.size();
    }

    void dfs(int u) {
        if(u == n) {
            res.push_back(path);
            return;
        }

        for(int i = 0; i < n; i ++ ) {
            if(!col[i] && !dg[u - i + n] && !udg[u + i]) {
                col[i] = dg[u - i + n] = udg[u + i] = true;
                path[u][i] = 'Q';
                dfs(u + 1);
                path[u][i] = '.';
                col[i] = dg[u - i + n] = udg[u + i] = false;
            }
        
        }
    }
};

LeetCode 53. 最大子序和

  1. 设 f(i) 表示以第 i 个数字为结尾的最大连续子序列的 总和 是多少。
  2. 初始化f(0) = nums[0]
  3. 动态转移方程f(i) = max( f(i - 1) + nums[i] , nums[i])
    即下一步的决策有两种选择
    (1)nums[i] 加上前面序列的和
    (2)以nums[i]为开始的新的数字序列
  4. 最终答案res = max(f(i)), 0 <= i < n
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n + 1, INT_MIN);
        f[0] = nums[0];
        for(int i = 1; i < n; i ++ ) {
            f[i] = max(nums[i], f[i - 1] + nums[i]);
        }
        int res = INT_MIN;
        for(int i = 0; i <= n; i ++ ) {
            res = max(res, f[i]);
        }
        return res;
    }
};

可以使用单一一个变量 s 代替 f 数组将空间复杂度优化到一个常数的效果

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int res = INT_MIN;
        for(int i = 0, s = 0; i < nums.size(); i ++ ) {
            if(s < 0) s = 0;
            s += nums[i];
            res = max(res, s);
        }
        return res;
    }
};

分治法

  1. 对于一个区间 [l,r],维护四个值,分别是:总和 sum;非空最大子段和 s;前缀非空最大子段和 ls;后缀非空最大子段和 rs。
  2. 分别递归左右子区间。
  3. 合并时,对于 sum 则是左右子区间的 sum 之和。
  4. 对于 s,则有三种情况取最大值:左区间的 s;右区间的 s;左区间的 rs 加上右区间的 ls。
  5. 对于 ls,则有两种情况取最大值:左区间的 ls;左区间的 sum 加上右区间的 ls。
  6. 对于 rs 同理。
  7. 合并后返回递归的结果。
class Solution {
public:

    struct Node {
        int sum, s, ls, rs;
    };

    Node build(vector<int>& nums, int l, int r) {
        if(l == r) {
            int v = max(nums[l], 0);
            return {nums[l], v, v, v};
        }

        int mid = (l + r) >> 1;
        auto L = build(nums, l, mid);
        auto R = build(nums, mid + 1, r);
        Node res;
        res.sum = L.sum + R.sum;
        res.s = max(max(L.s, R.s), L.rs + R.ls);
        res.ls = max(L.ls, L.sum + R.ls);
        res.rs = max(R.rs, R.sum + L.rs);
        return res;
    }

    int maxSubArray(vector<int>& nums) {
        int res = INT_MIN;
        for(auto x : nums) res = max(res, x);
        if(res < 0) return res;
        auto t = build(nums, 0, nums.size() - 1);
        return t.s;
    }


};

LeetCode 54. 螺旋矩阵

蛇形矩阵的变化题目,做法类似于BFS
在这里插入图片描述

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> res;
        int n = matrix.size();
        if(!n) return res;
        int m = matrix[0].size();
        
        //作为方向偏移量
        int dx[] = {0, 1, 0, -1};
        int dy[] = {1, 0, -1, 0};
        //记录对应的数字是否被遍历过
        vector<vector<bool>> st(n, vector<bool>(m));

        for(int i = 0, x = 0, y = 0, d = 0; i < n * m; i ++ ) {
            res.push_back(matrix[x][y]);
            st[x][y] = true;

            int a = x + dx[d], b = y + dy[d];
            if(a < 0 || a >= n || b < 0 || b >= m || st[a][b]) {
                d = (d + 1) % 4;
                a = x + dx[d], b = y + dy[d];
            }

            x = a, y = b;
        } 
        return res;
    }
};

LeetCode 55. 跳跃游戏

与LeetCode 45. 跳跃游戏 II 类似
用一个临时变量 cur 记录当前能跳到的离起点最远的距离是多少
如果当前i > cur那么就表示无法继续进行跳跃,那么就不能跳到终点
如果能成功走完for循环,那么就能够跳到终点

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n = nums.size();
        int cur = 0;
        for(int i = 0; i < n; i ++ )
        {
            if(i > cur) return false;
            cur = max(cur, i + nums[i]);
        }
        return true;
    }
};

LeetCode 56. 合并区间

思想方法类似于贪心

  1. 先给每个区间按左端点排序
  2. 遍历每个区间
    如果第二个区间起点小于第一个区间终点,那么就是有交集,将第一个区间的终点更新为第二个区间的终点
    如果没有交集,那么将前面更新完毕的区间存储起来
    在这里插入图片描述
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& segs) {
        if(segs.size() <= 1) return segs; 
        
        vector<vector<int>> res;
        sort(segs.begin(), segs.end());
        int st = segs[0][0], ed = segs[0][1];
        for(auto seg : segs)
        {
            if(seg[0] > ed)
            {
                res.push_back({st, ed});
                st = seg[0], ed = seg[1];
            }
            else ed = max(ed, seg[1]);
        }
        res.push_back({st, ed});
        return res;
    }
};

LeetCode 57. 插入区间

在这里插入图片描述

(一)思路类比LeetCode 56. 合并区间
将新的区间插入原有区间后排序,再执行合并的操作

class Solution {
public:
    vector<vector<int>> insert(vector<vector<int>>& segs, vector<int>& newSeg) {
        vector<vector<int>> res;
        if(segs.empty()){
            res.push_back(newSeg);
            return res;
        }
        segs.push_back(newSeg);

        sort(segs.begin(), segs.end());

        int st = segs[0][0], ed = segs[0][1];
        for(auto seg : segs)
        {
            if(seg[0] > ed)
            {
                res.push_back({st, ed});
                st = seg[0], ed = seg[1];
            }
            else ed = max(ed, seg[1]);
        }
        res.push_back({st, ed});
        return res;
    }
};

(二)更简洁的 O ( n ) O(n) O(n) 做法

  1. 将原区间数组中左边和新区间没有交集的部分添加进res数组
  2. 合并与新区间有交集的区间
  3. 将合并完得到的区间添加进res数组
  4. 将位于新区间右边的部分添加进res数组
class Solution {
public:
    vector<vector<int>> insert(vector<vector<int>>& segs, vector<int>& newSeg) {
        vector<vector<int>> res;
        int k = 0;
        //将原区间数组中左边和新区间没有交集的部分添加进res数组
        while(k < segs.size() && segs[k][1] < newSeg[0]) 
            res.push_back(segs[k ++ ]);
		
		//合并与新区间有交集的区间
        if(k < segs.size()) {
            newSeg[0] = min(newSeg[0], segs[k][0]);
            while(k < segs.size() && segs[k][0] <= newSeg[1])
                newSeg[1] = max(newSeg[1], segs[k ++ ][1]);
        }
        
		//将合并完得到的区间添加进res数组
        res.push_back(newSeg);
        
		//将位于新区间右边的部分添加进res数组
        while(k <segs.size()) res.push_back(segs[k ++ ]);
        return res;
    }
};

LeetCode 58. 最后一个单词的长度

(一)双指针算法,用空格的位置计算长度

class Solution {
public:
    int lengthOfLastWord(string s) {
        if(s == "" || s == " ") return 0;
        
        //去掉空格
        while(s.back() == ' ') s.pop_back();
        int pre = 0, i = 1;
        
        //记录上一个空格的位置
        for(; i <= s.size(); i ++ )
            if(s[i - 1] == ' ')
                pre = i;
                
        pre ++ ;
        return i - pre;
    }
};

(二)从后往前遍历

class Solution {
public:
    int lengthOfLastWord(string s) {
        for(int i = s.size() - 1; i >= 0; i -- ) {
            if(s[i] == ' ') continue;
            int j = i - 1;
            while(j >= 0 && s[j] != ' ') j -- ;
            return i - j;
        }
        return -1;
    }
};

(三) stringstream的用法

class Solution {
public:
    int lengthOfLastWord(string s) {
        stringstream ssin(s);
        int res = 0;
        string word;
        while(ssin >> word) res = word.size();
        return res;
    }
};

LeetCode 59. 螺旋矩阵 II

思路和LeetCode 54. 螺旋矩阵完全一致,稍作修改即可
在这里插入图片描述

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0));
        int dx[] = {0, 1, 0, -1};
        int dy[] = {1, 0, -1, 0};

        for(int i = 0, d = 0, x = 0, y = 0; i < n * n; i ++ ) {
            res[x][y] = i + 1;
            int a = x + dx[d], b = y + dy[d];
            if(a < 0 || b < 0 || a >= n || b >= n || res[a][b]){
                d = (d + 1) % 4;
                a = x + dx[d], b = y + dy[d];
            }
            x = a, y = b;
        }
        return res;
    }
};

LeetCode 60. 排列序列

nextPermutation

class Solution {
public:
    string res;
    string getPermutation(int n, int k) {
        for(int i = 1; i <= n; i ++ ) res += to_string(i);
        for(int i = 0; i < k - 1; i ++ )
            next_permutation(res.begin(), res.end());

        return res;
    }
};

爆搜,会超时

class Solution {
public:

    vector<string> res;
    int n;

    string getPermutation(int _n, int k) {
        n = _n;
        vector<bool> st(n, false);
        dfs(0, "", st);
        return res[k - 1];
    }

    void dfs(int u, string path, vector<bool>& st) {
        if(u == n) {
            res.push_back(path);
            return;
        }

        for(int i = 0; i < n; i ++ ) {
            if(!st[i]) {
                st[i] = true;
                path += ('1' + i);
                dfs(u + 1, path, st);
                path.pop_back();
                st[i] = false;
            }
        }
    }
};

回溯 + 剪枝
剪枝思路:将搜索树的无关节点跳过
为了便于理解,我们这里给出一个例子的具体操作:n=4,k=14
首先我们将所有排列按首位分组:

  1. 1 + (2, 3, 4的全排列)

  2. 2 + (1, 3, 4的全排列)

  3. 3 + (1, 2, 4的全排列)

  4. 4 + (2, 3, 4的全排列)
    接下来我们确定第 k=14k=14 个排列在哪一组中。每组的排列个数是 3!=6 个,所以第14个排列在第3组中,所以首位已经可以确定,是3。
    然后我们再将第3组的排列继续分组:

  5. 31 + (2, 4的全排列)

  6. 32 + (1, 4的全排列)

  7. 34 + (1, 2的全排列)
    接下来我们判断第 k=14 个排列在哪个小组中。我们先求第 14 个排列在第三组中排第几,由于前两组每组有6个排列,所以第14个排列在第3组排第 14−6∗2=2
    在第三组中每个小组的排列个数是 2!=2个,所以第 k 个排列在第1个小组,所以可以确定它的第二位数字是1。

依次类推,可以推出第14个排列是 3142。
图示:
在这里插入图片描述

class Solution {
public:
    vector<bool> st;
    
    //阶乘打表
    int arr[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
    int n, k;

    string getPermutation(int _n, int _k) {
        n = _n, k = _k;
        st.resize(n + 1);

        string path;
        dfs(0, path);
        
        return path;
    }

    void dfs(int u, string& path) {
        if (u == n) return;
        
        
        int cnt = arr[n - 1 - u];
        for (int i = 1; i <= n; i ++) {
            if (st[i]) continue;

            if (cnt < k) {
                k -= cnt;
                continue;
            }

            path += (i + '0');
            st[i] = true;
            dfs(u + 1, path);
            return;
        }
    }
};

循环计数法,思路与上述的回溯剪枝思路一致

class Solution {
public:
    string getPermutation(int n, int k) {
        string res;
        vector<bool> st(10);
        for(int i = 0; i < n; i ++ ) {
            int fact = 1;

			//阶乘数
            for(int j = 1; j <= n - i - 1; j ++ ) fact *= j;

            for(int j = 1; j <= n; j ++ ) {
                if(!st[j]) {
                	//如果k不在这个分支里面
                    if(fact < k) k -= fact;
                    else {
                        res += to_string(j);
                        st[j] = true;
                        break;
                    }
                }
            }
        }
        return res; 
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值