LeetCode学习计划——算法入门

Day1 二分查找

1、二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

class Solution {
public:
//在升序数组 nums 中寻找目标值 target,对于特定下标 i,比较 nums[i] 和 target 的大小:
//如果 nums[i]=target,则下标 i 即为要寻找的下标;
//如果 nums[i]>target,则 target 只可能在下标 i 的左侧;
//如果 nums[i]<target,则 target 只可能在下标 i 的右侧。
//基于上述事实,可以在有序数组中使用二分查找寻找目标值。

    int search(vector<int>& nums, int target) {
        //定义比较的个数  首位和末位
        int low = 0;
        int high = nums.size() - 1;
        //while循环遍历 比较
        while(low <= high)
        {
            //定义要比较的中间位
            int mid = (high - low) / 2 + low;
            if(nums[mid] == target) 
            {
                return mid;//如果 nums[i]=target,则下标 i 即为要寻找的下标
            }
            else if(nums[mid] > target)
            {
                high = mid - 1;//如果 nums[i]>target,则 target 只可能在下标 i 的左侧
            }
            else 
            {
                low = mid + 1;//如果 nums[i]<target,则 target 只可能在下标 i 的右侧
            }
        }
        return -1; // 若不存在mid,则返回-1

    }
};

2、第一个错误的版本
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        //二分查找法  
        //定义首位和末位
        int low = 1;
        int high = n;
        //循环遍历
        while(low < high)
        {
            //定义中间位
            int mid = (high - low) / 2 + low;// 防止计算时溢出
            
            if(isBadVersion(mid))
            {
                high = mid;  // 答案在区间 [left, mid] 中
            }
            else
            {
                low = mid + 1; // 答案在区间 [mid + 1, high] 中
            }
        }
        return low; // 此时有 low == high,区间缩为一个点,即为答案
    }
};

3、搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        //二分法
        int n = nums.size();
        int left = 0;
        int right = n-1;

        while(left<=right)
        {
            int mid = left+(right-left)/2;  //等同于(right+left)/2,避免溢出     //(a>>b)即为a/2^b

            if(nums[mid]<target)
            {
                left = mid+1;
            }
            else
            {
                right = mid-1;
            }
        }
        return left;  //循环结束条件为left > right  ,left==right+1

    }
};

Day2 双指针

1、有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

class Solution {
public:
//如果数组 nums 中的所有数都是非负数,那么将每个数平方后,数组仍然保持升序;如果数组nums 中的所有数都是负数,那么将每个数平方后,数组会保持降序。
//这样一来,如果我们能够找到数组 nums 中负数与非负数的分界线,那么就可以用类似「归并排序」的方法了。具体地,我们设neg 为数组 nums 中负数与非负数的分界线,也就是说,nums[0] 到 nums[neg] 均为负数,而 nums[neg+1] 到 nums[n−1] 均为非负数。当我们将数组 nums 中的数平方后,那么 nums[0] 到 nums[neg] 单调递减,nums[neg+1] 到 nums[n−1] 单调递增。
//由于我们得到了两个已经有序的子数组,因此就可以使用归并的方法进行排序了。具体地,使用两个指针分别指向位置neg 和 neg+1,每次比较两个指针对应的数,选择较小的那个放入答案并移动指针。当某一指针移至边界时,将另一指针还未遍历到的数依次放入答案。

    vector<int> sortedSquares(vector<int>& nums) {
        int n = nums.size();
        int neg = -1;
        for(int i = 0;i < n;i++)
        {
            if(nums[i] < 0)
            {
                neg = i;
            }
            else
            {
                break;
            }
        }

        vector<int> ans;//定义一个空向量 push_back 压入数据
        int i = neg;
        int j = neg + 1;

        while(i >= 0 || j < n)
        {
            if(i < 0)
            {
                ans.push_back(nums[j] * nums[j]);
                ++j;
            }
            else if(j == n)
            {
                ans.push_back(nums[i] * nums[i]);
                --i;
            }
            else if(nums[i] * nums[i] < nums[j] * nums[j])
            {
                ans.push_back(nums[i] * nums[i]);
                --i;
            }
            else
            {
                ans.push_back(nums[j] * nums[j]);
                ++j;
            }
        }

        return ans;

    }
};

2、轮转数组
给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

class Solution {
public:
//可以使用额外的数组来将每个元素放至正确的位置。用 n 表示数组的长度,我们遍历原数组,将原数组下标为 i 的元素放至新数组下标为 (i+k) mod n 的位置,最后将新数组拷贝至原数组即可。

    void rotate(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> newArr(n);

        for(int i = 0;i < n;i++)
        {
            newArr[(i + k) % n] = nums[i];
        }

        nums.assign(newArr.begin(),newArr.end());

    }
};

Day3 双指针

1、移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        //左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部
        int n = nums.size();
        int left = 0, right = 0;

        while(right < n){
            if(nums[right]){
                swap(nums[left], nums[right]); 
                left++;             
            }            
            right++;
        }

    }
};

2、两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int n = numbers.size() - 1;
        int left = 0, right = n;//左指针指向第一个,右指针指向最后一个

        while(left < right){
            if((numbers[left] + numbers[right]) == target){
                return {left + 1, right + 1};
            }
            else if((numbers[left] + numbers[right]) > target){
                right--;
            }
            else {
                left++;
            }
        }

        return {-1, -1};
    }    
};

Day4 双指针

1、反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

class Solution {
public:
    void reverseString(vector<char>& s) {
        int n = s.size();
        int slow = 0, fast = n - 1;

        while(slow < fast){
            swap(s[slow], s[fast]);
            fast--;
            slow++;
        }
        return;
    
    }
};

2、反转字符串中的单词 III
给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

class Solution {
public:
    string reverseWords(string s) {
        int n = s.length();
        int i = 0;//每个单词长度

        while(i < n){

            int start = i;

            while(i < n && s[i] != ' '){
                i++;//i是单词长度
            }

            int left = start, right = i - 1;
            while(left < right){
                swap(s[left], s[right]);
                left++;
                right--;
            }

            while(i < n && s[i] == ' '){
                i++;
            }    

        }
        return s;
        
    }
};

Day5 双指针

1、链表的中间结点
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        //slow 一次走一步,fast 一次走两步。那么当 fast 到达链表的末尾时,slow 必然位于中间。
        ListNode *slow = head;
        ListNode *fast = head;

        while(fast != NULL && fast->next != NULL){
            slow = slow->next;
            fast = fast->next->next;
        }

        return slow;

    }
};

2、删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *dummyHead = new ListNode(0);//虚拟头结点
        dummyHead->next = head;
        ListNode *slow = dummyHead;
        ListNode *fast = dummyHead;

        //fast先走n+1步,当fast走到尾,删除slow的下一个:slow->next = slow->next->next
        while(n-- && fast != NULL){
            fast = fast->next;
        }
        fast = fast->next;

        while(fast != NULL){
            slow = slow->next;
            fast = fast->next;
        }

        slow->next = slow->next->next;

        return dummyHead->next;

    }
};

Day6 滑动窗口

1、无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_set<char> occ;
        int n = s.size();

        //右指针
        int right = -1;
        int ans = 0;
        //左指针
        for(int i = 0; i < n; i++)
        {
            if(i != 0) //左指针右移
            {
                occ.erase(s[i - 1]);//移除一个字符
            }

            while(right + 1 < n && ! occ.count(s[right + 1]))//且occ中元素统计结果为0,即occ中不存在该元素,count返回0
            {
                occ.insert(s[right + 1]); //插值
                right++;
            }//右指针右移

            ans = max(ans , right - i + 1);

        }
        return ans;
    } 
};

2、字符串的排列
给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。
换句话说,s1 的排列之一是 s2 的 子串 。

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        //cnt1统计中各个字符的个数,cnt2​统计当前遍历的子串中各个字符的个数
        
        int n = s1.length();
        int m = s2.length();

        if(n > m)
        {
            return false;
        }

        vector<int> cnt1(26) , cnt2(26);

        for(int i = 0;i < n;i++)
        {
            cnt1[s1[i] - 'a']++;
            cnt2[s2[i] - 'a']++;
        }
        if(cnt1 == cnt2)
        {
            return true;
        }

        for(int i = n;i < m;i++)
        {
            cnt2[s2[i] - 'a']++;
            cnt2[s2[i - n] - 'a']--;
        
            if(cnt1 == cnt2)
            {
                return true;
            }
        }
        return false;
    }
};

Day7 广度优先搜索 / 深度优先搜索

1、图像渲染
有一幅以 m x n 的二维整数数组表示的图画 image ,其中 image[i][j] 表示该图画的像素值大小。
你也被给予三个整数 sr , sc 和 newColor 。你应该从像素 image[sr][sc] 开始对图像进行 上色填充 。
为了完成 上色工作 ,从初始像素开始,记录初始坐标的 上下左右四个方向上 像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应 四个方向上 像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为 newColor 。
最后返回 经过上色渲染后的图像 。

class Solution {
public:
    const int dx[4] = {1, 0, 0, -1};//向上和向下
    const int dy[4] = {0, 1, -1, 0};//向左和向右 
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
        //保存当前颜色
        int curColor = image[sr][sc];
        if(image[sr][sc] == newColor) return image;//初始颜色即是渲染颜色 返回

        int n = image.size();//行数
        int m = image[0].size();//列数

        queue<pair<int, int>> que;//定义队列 pair代表有两个属性 访问分别用first second
        que.emplace(sr, sc);
        image[sr][sc] = newColor;

        while(!que.empty()){
            int mx = que.front().first, my = que.front().second;//选一个块
            que.pop();//把该块推出去

            //对该块的四个方向做处理
            for(int i = 0; i < 4; i++){//4个方向
                int x = mx + dx[i], y = my + dy[i];
                
                if(x >= 0 && x < n && y >= 0 && y < m && image[x][y] == curColor){
                    que.emplace(x, y);
                    image[x][y] = newColor;
                }

            }
        }
        return image;

    }
};

2、岛屿的最大面积
给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。
计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。

class Solution {
public:

    int getArea(vector<vector<int>>& grid, const int &i, const int &j){
        if(i == grid.size() || i < 0) return 0;
        if(j == grid[0].size() || j < 0) return 0;

        if(grid[i][j] == 1){
            grid[i][j] = 0;//避免重复搜索
            return 1 + getArea(grid, i + 1, j) + getArea(grid, i - 1, j) + getArea(grid, i, j + 1) + getArea(grid, i, j - 1);
        }

        return 0;

    }
    
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        int m = grid.size();//行
        int n= grid[0].size();//列

        int area = 0,maxArea = 0;

        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid[i][j] == 1){
                    area = getArea(grid, i, j);
                    maxArea = maxArea > area ? maxArea : area; 
                }
            }
        }

        return maxArea;

    }
};

Day8 广度优先搜索 / 深度优先搜索

1、合并二叉树
给你两棵二叉树: root1 和 root2 。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。

/**
 * 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* mergeTrees(TreeNode* root1, TreeNode* root2) {        

        if(root1 == NULL && root2 == NULL) return NULL;
        else if(root1 == NULL && root2 != NULL) return root2;
        else if(root1 != NULL && root2 == NULL) return root1;

        else{//两个都不为空
            auto res = new TreeNode(root1->val + root2->val);//
            res->left = mergeTrees(root1->left, root2->left);
            res->right = mergeTrees(root1->right, root2->right);
            
            return res;
        }

    }
};

2、填充每个节点的下一个右侧节点指针
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;
    Node* next;

    Node() : val(0), left(NULL), right(NULL), next(NULL) {}

    Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}

    Node(int _val, Node* _left, Node* _right, Node* _next)
        : val(_val), left(_left), right(_right), next(_next) {}
};
*/

//层序遍历
class Solution {
public:
    Node* connect(Node* root) {
        if(root == nullptr) return root;

        queue<Node*> que;
        que.push(root);

        while(!que.empty()){
            int size = que.size();
            
            for(int i = 0; i < size; i++){
                Node* node = que.front();
                que.pop();

                if(i < size - 1){
                    node->next = que.front();
                }

                // 拓展下一层节点
                if(node->left != nullptr){
                    que.push(node->left);
                }
                if(node->right != nullptr){
                    que.push(node->right);
                }
                
            }

        }

        return root;
        
    }
};

Day9 广度优先搜索 / 深度优先搜索

1、01 矩阵
给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。

class Solution {
public:
    //动态规划
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
        int m = mat.size();
        int n = mat[0].size();
        vector<vector<int>> dp(m, vector<int>(n, 0));

        //初始化
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(mat[i][j])
                    dp[i][j] = 1e9;
            }
        }

        //左上角
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(dp[i][j] != 0){
                    if(i - 1 >= 0){
                        dp[i][j] = min(dp[i][j], dp[i - 1][j] + 1);
                    }
                    if(j - 1 >= 0){
                        dp[i][j] = min(dp[i][j], dp[i][j - 1] + 1);
                    }              
                }
            }
        }

        //右下角
        for(int i = m - 1; i >= 0; i--){
            for(int j = n - 1; j >= 0; j--){
                if(dp[i][j] != 0){
                    if(i + 1 < m){
                        dp[i][j] = min(dp[i][j], dp[i + 1][j] + 1);
                    }
                    if(j + 1 < n){
                        dp[i][j] = min(dp[i][j], dp[i][j + 1] + 1);
                    }
                }
            }
        }

        return dp;

    }
};

2、腐烂的橘子
在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。
返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。

class Solution {
public:
//首先分别将腐烂的橘子和新鲜的橘子保存在两个集合中;
//模拟广度优先搜索的过程,方法是判断在每个腐烂橘子的四个方向上是否有新鲜橘子,如果有就腐烂它。每腐烂一次时间加 1,并剔除新鲜集合里腐烂的橘子;
//当橘子全部腐烂时结束循环。

    const int dx[4] = {1, 0, 0, -1};//向上和向下
    const int dy[4] = {0, 1, -1, 0};//向左和向右 
    int orangesRotting(vector<vector<int>>& grid) {
        int minTime = 0;//时间
        int fresh = 0;//新鲜橘子数量
        queue<pair<int, int>> que;//腐烂橘子队列

        int m = grid.size(), n = grid[0].size();

        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid[i][j] == 1) fresh++;
                else if(grid[i][j] == 2) que.push({i, j});
            }
        }

        while(!que.empty()){
            int size = que.size();
            bool flag = false; //记录时间的变化

            for(int k = 0; k < size; k++){
                int mx = que.front().first, my = que.front().second;//拿出腐烂橘子的那块   
                que.pop();//推出去
                
                for(int i = 0; i < 4; i++){//四个方向
                    int x = mx + dx[i], y = my + dy[i];

                    if(x < m && x >= 0 && y < n && y >= 0 && grid[x][y] == 1){
                        grid[x][y] = 2;//使其腐烂
                        que.push({x, y});//存入腐烂橘子队列
                        fresh--;//新鲜橘子数量减一
                        flag = true;//记录一次时间                     
                    }  

                }
                
            }
            //cout<<minTime<<endl;
            if(flag) minTime++;
        }

        return fresh ? -1 : minTime; //如果还有新鲜橘子返回 -1

    }
};

Day10 递归 / 回溯

1、合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}   //初始化时使用,将x和next赋值给val和next;
                                                                    //val即为第一个值,next为后面的数
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {   //merge函数就是把两个有序的链表合并为一个有序的链表
    /**
    //法一:
    if(l1 ==nullptr)
    {
        return l2;
    }
    else if(l2 == nullptr)
    {
        return l1;
    }
    else if(l1->val < l2->val)
    {
        l1->next = mergeTwoLists(l1->next,l2);
        return l1;
    }
    else
    {
        l2->next = mergeTwoLists(l1,l2->next);
        return l2;
    }
    **/
	
	//法二:
    ListNode *preHead = new ListNode(-1);
    ListNode *prev = preHead;

    while(l1 != NULL && l2 != NULL){
        if(l1->val < l2->val){
            prev->next = l1;
            l1 = l1->next;
        }
        else{
            prev->next = l2;
            l2 = l2->next;
        }
        prev = prev->next;
    }
    //循环完之后,最多有一个为非空,直接拼接在后面
    prev->next = l1 == nullptr ? l2 : l1;

    return preHead->next;

    }
};

2、反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *tmp;//临时保存cur的下一个节点
        ListNode *cur = head;
        ListNode *pre = NULL;

        while(cur){
            tmp = cur->next;
            cur->next = pre;

            pre = cur;
            cur = tmp;
        }

        return pre;

    }
};

Day11 递归 / 回溯

1、组合
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;

    void backtracking(int n, int k, int startIndex){
        //终止条件
        if(path.size() == k){
            res.push_back(path);
            return;
        }

        //单层逻辑
        for(int i = startIndex; i <= n; i++){
            path.push_back(i);
            backtracking(n, k, i + 1);//递归
            path.pop_back();//回溯
        }
    }

    vector<vector<int>> combine(int n, int k) {
        res.clear();
        path.clear();

        backtracking(n, k, 1);

        return res;

    }
};

2、全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;

    //used记录使用了哪些数字
    void backtracking(vector<int> &nums, vector<bool> &used){
        if(path.size() == nums.size()){
            res.push_back(path);
            return;
        }

        for(int i = 0; i < nums.size(); i++){
            if(used[i] == true){//该数字已使用
                continue;//判断下一位数字
            }
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums, used);
            path.pop_back();//回溯
            used[i] = false;
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        res.clear();
        path.clear();

        vector<bool> used(nums.size(), false);
        backtracking(nums, used);

        return res;
    }
};

3、字母大小写全排列
给定一个字符串 s ,通过将字符串 s 中的每个字母转变大小写,我们可以获得一个新的字符串。
返回 所有可能得到的字符串集合 。以 任意顺序 返回输出。

class Solution {
public:
//A 65   a 97
    vector<string> res;
    int number = 'a' - 'A'; //32

    void backtracking(string s, int startIdx){
        //终止条件
        if(startIdx == s.size()){
            res.push_back(s);
            return;
        }

        //单层逻辑
        backtracking(s, startIdx + 1);//数字
        if(s[startIdx] >= 'A') {
            s[startIdx] ^= 32;//大小写转换
            backtracking(s, startIdx + 1);
        }  
    }

    vector<string> letterCasePermutation(string s) {
        res.clear();
        backtracking(s, 0);
        return res;
    }
};

Day12 动态规划

1、爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

class Solution {
public:
    int climbStairs(int n) { 
        /**
        //法一:
        int p=0,q=0,r=1;
        for(int i=1;i<=n;i++)
        {
            p=q;
            q=r;
            r=p+q; //f(x)=f(x-1)+f(x-2)
        }
        return r;
        **/

		//法二:动态规划
        if(n <= 1) return n;

        vector<int> dp(n + 1);
        dp[0] = 1;
        dp[1] = 1;

        for(int i = 2; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }

        return dp[n];
    }
};

2、打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

class Solution {
public:
    int rob(vector<int>& nums) {
        //dp[i] = max(dp[i-2] + nums[i] , dp[i-1]);

        int n = nums.size();

        if(n == 0)
        {
            return 0;
        }

        if(n == 1)
        {
            return nums[0];
        }

        //vector<int> dp = vector<int> (n , 0);
        vector<int> dp(n);
        dp[0] = nums[0];
        dp[1] = max(nums[0],nums[1]);
        
        for(int i = 2;i < n;i++)
        {
            dp[i] = max(dp[i-2] + nums[i] , dp[i-1]);
        }
        return dp[n - 1];

    }
};

3、三角形最小路径和
给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        
        int m = triangle.size();
        if(m == 1) return triangle[0][0];

        vector<vector<int>> dp(m, vector<int>(m));//造一个新的二维数组,返回最后一行的最小值
        dp[0][0] = triangle[0][0];

        for(int i = 1; i < m; i++){
            //j = 0  //最左边
            dp[i][0] = dp[i - 1][0] + triangle[i][0];

            for(int j = 1; j < i; j++){
                dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j];
            }  

            //j = i  //最右边
            dp[i][i] = dp[i - 1][i - 1] + triangle[i][i];                    
            
        }

        return *min_element(dp[m - 1].begin(), dp[m - 1].end());
    }
};

Day13 位运算

1、2 的幂
给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。
如果存在一个整数 x 使得 n == 2^x ,则认为 n 是 2 的幂次方。

class Solution {
public:
    bool isPowerOfTwo(int n) {
       //n & (n - 1) == 0 则为2的幂
       return n > 0 && (n & (n - 1)) == 0;
    }
};

2、位1的个数
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
提示:
请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int num = 0;

        while(n){
            num++;
            n = n & (n - 1); //相与求1个数
        }

        return num;
        
    }
};

Day14 位运算

1、颠倒二进制位
颠倒给定的 32 位无符号整数的二进制位。

class Solution {
public:
    uint32_t reverseBits(uint32_t n) {
        uint32_t res = 0;

        for(int i = 0; i < 32; i++){
            res = (n & 1) | (res << 1);//n & 1取出最后一位, 再与 res << 1(最后一位为0) 相或即可得到该位的值
            n = n >> 1;//n的末位右移一位,倒数下一位
        }
        return res;
        
    }
};

2、只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

class Solution {
public:
    int singleNumber(vector<int>& nums) {

        //遍历 异或
        //任何数和 0 做异或运算,结果仍然是原来的数,即 a⊕0=a。
        //任何数和其自身做异或运算,结果是 0,即 a⊕a=0。

        int res = 0;
        for(auto e:nums){
            res ^= e;
        }
        return res;

    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wrdoct

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值