1、双指针

双指针法是最简单的一种了,大概就是通过两个变量,作为数组或者字符串的下标索引进行操作
双指针一共分为三种,分为快慢指针、左右指针、滑动窗口
左右指针一般就是while(left<right)
................................
力扣3题:给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
................................
双指针法滑动窗口:两个变量为字符串起始位置索引,一个是左指针,一个右指针,当左指针指向内容和右指针指向的内容不一样,右指针++,
一样的话,左指针等于右指针,右指针++。期间左右指针指向的内容是否相等用了一个循环去判断

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s.size() <= 1)
            return s.size();
        int res = 0;
        int len = 1;
        int left = 0;
        int right = 1;
        while(right < s.size())
        {
            len = 1;
            int tmp = right;
            for(int i=left;i<tmp;i++)
            {
                if(s[right] == s[i])
                {
                    left++;
                    right = left+1;
                    break;
                }
                if(s[right] != s[i])
                    len++; 
                if(i == right-1 && s[right] != s[i])
                    right++;
            }
            res = max(res,len);
        }
        return res;
    }
};

................................
力扣15题:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
................................
通过固定一个left,找另外两个数。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        if(nums.size() < 3)
            return {};
        vector<vector<int>> res;
        sort(nums.begin(),nums.end());
        int left = 0 , right = nums.size()-1 , point = 1;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[0] > 0)
                return {};
            left = i;
            point = i+1;
            right = nums.size()-1;
            if(i>0 && nums[i] == nums[i-1])
                continue;
            while(point < right)
            {
                if(nums[left] + nums[point] + nums[right] == 0)
                {
                    vector<int> tmp;
                    tmp.push_back(nums[left]);
                    tmp.push_back(nums[point]);
                    tmp.push_back(nums[right]);
                    res.push_back(tmp);
                    point++;
                    right--;
                    continue;
                }
                if(nums[left] + nums[point] + nums[right] > 0)
                    right--;
                if(nums[left] + nums[point] + nums[right] < 0)
                    point++;
            }
        }
        res.erase(unique(res.begin(),res.end()),res.end());
        return res;
    }
};

2、链表

链表也算比较简单的数据结构,在LeetCode中题目中传进来的头结点都是直接指向链表头的。比如一个链表1->2->3->4.在LeetCode中的head头结点都是直接指向1这个节点的。而不是head->next才指向1.
链表的题一般都涉及链表的翻转啊,链表的指向的迭代问题。一些环形链表的判断也涉及双指针快慢指针的事情。
经典的题就是链表翻转、合并排序链表、两两反转链表、环形链表。
一般处理链表问题,都会新建一个dummy的头结点,这样更改了head依旧可以返回链表
链表问题常规思路:反转链表,遍历链表,写代码时注意用到哪个的next就要判断是否为空while(cur->next && cur->next->next)
...................................
力扣206题:反转一个单链表。
...................................

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *newhead = NULL;
        while(head)
        {
            ListNode *t = head->next;
            head->next = newhead;
            newhead = head;
            head = t;
        }
        return newhead;
    }
};

...............................................
力扣23题:给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
...............................................

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode *res = NULL;
        for(int i=0;i<lists.size();++i)
        {
            res = mergeTwoList(lists[i], res);
        }
        return res;
    }

    ListNode* mergeTwoList(ListNode* l1,ListNode* l2)
    {
        ListNode* dummy = new ListNode(-1), *cur=dummy;
        while(l1 && l2)
        {
            if(l1->val <= l2->val)
            {
                cur->next = l1;
                l1 = l1->next;
            }
            else
            {
                cur->next = l2;
                l2 = l2->next;
            }
            cur = cur->next;
        }
        cur->next = l1?l1:l2;
        return dummy->next;
    }
};

...................................
给定一个链表判断是否有环
...................................

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if (head == nullptr || head->next == nullptr) 
            return false;
        ListNode *low = head;
        ListNode *fast = head;
        while(fast!=NULL && fast->next!=NULL )
        {
            fast = fast->next->next;
            low = low->next;
            if(fast == low)
                return true;
        }
        return false;
    }
};

........................
两两交换链表中的节点
.......................

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummy = new ListNode(-1),*cur = dummy;
        dummy->next = head;
        while(cur->next && cur->next->next)
        {
            ListNode *t = cur->next->next;
            cur->next->next = t->next;
            t->next = cur->next;
            cur->next = t;
            cur = cur->next->next;
        }
        return dummy->next;
    }
};

3、单调栈

单调栈分为单调递增栈和单调递减栈
1.1. 单调递增栈即栈内元素保持单调递增的栈
1.2. 同理单调递减栈即栈内元素保持单调递减的栈

操作规则(下面都以单调递增栈为例)
2.1. 如果新的元素比栈顶元素大,就入栈
2.2. 如果新的元素较小,那就一直把栈内元素弹出来,直到栈顶比新元素小

加入这样一个规则之后,会有什么效果
3.1. 栈内的元素是递增的
3.2. 当元素出栈时,说明这个新元素是出栈元素向后找第一个比其小的元素

代码模板

stack<int> st;
for(int i = 0; i < nums.size(); i++)
{
	while(!st.empty() && st.top() > nums[i])
	{
		st.pop();
	}
	st.push(nums[i]);
}

......................................
力扣84题:给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。
......................................

class Solution {
public:
    int largestRectangleArea(vector<int> &heights) {
        unsigned long size = heights.size();
        if (size == 1) 
            return heights[0];
        int res = 0;
        stack<int> stk;
        for (int i = 0; i < size; ++i) 
        {
            while (!stk.empty() && heights[stk.top()] > heights[i]) 
            {
                int length = heights[stk.top()];
                stk.pop();
                int weight = i;
                if (!stk.empty()) 
                {
                    weight = i - stk.top() - 1;
                }
                res = max(res, length * weight);
            }
            stk.push(i);
        }
        while (!stk.empty()) 
        {
            int length = heights[stk.top()];
            stk.pop();
            int weight = size;
            if (!stk.empty()) 
            {
                weight = size - stk.top() - 1;
            }
            res = max(res, length * weight);
        }
        return res;
    }
};

4、树

树的问题一般解法都很一般。
第一种解法就是递归,递归左子树,右子树。
第二种解法就是遍历:设计前序遍历、中序遍历、后序遍历、层次遍历
第三种就是一般碰见搜索二叉树,都会用到中序遍历的性质,搜索二叉树中序遍历后得到的内容是升序排列的
第四种就是深度优先搜索,遍历所有的路径,比如题目路径总和。
.....................
二叉树的中序遍历
.....................

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        func(root,res);
        return res;
    }

    void func(TreeNode *root,vector<int> &res)
    {
        if (root)
        {
            func(root->left,res);
            res.push_back(root->val);
            func(root->right,res);
        }
    }
};

..................
求二叉树最大深度
.................

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == NULL)
            return 0;
        int left = maxDepth(root->left);
        int right = maxDepth(root->right);
        return left>right?left+1:right+1;
    }
};

..................
二叉树层次遍历
.................

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (!root)
            return {};
        queue<TreeNode*> q{{root}};
        vector<vector<int>> res;
        while(!q.empty())
        {
            vector<int> orderLine;
            for (int i = q.size(); i >0 ; --i)//注意这里把q.size()放在初始化,因为循环中改变了q
            {
                TreeNode* t=q.front();
                q.pop();
                orderLine.push_back(t->val);
                if(t->left)
                    q.push(t->left);
                if(t->right)
                    q.push(t->right);
            } 
            res.push_back(orderLine);
        } 
        return res;
    }
};

.......................
对称二叉树
.......................

class Solution {
public:
    bool mySolution(TreeNode* root,TreeNode* root2) {
        if (root == NULL && root2 == NULL)
            return true;
        if (root == NULL && root2 != NULL || root != NULL && root2 == NULL)
            return false;
        if (root->val != root2->val)
            return false;
        return mySolution(root->left,root2->right) && mySolution(root->right,root2->left);
    }
    bool isSymmetric(TreeNode* root) 
    {
        return mySolution(root, root);
    }
};

............................
验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
............................

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        vector<int> res;
        orderTravel(root, res);
        for (int i=0;i<res.size()-1;i++)
        {
            if (res[i] >= res[i+1])
                return false;
        }
        return true;
    }

    void orderTravel(TreeNode *root, vector<int>& res)
    {
        if (root)
        {
            orderTravel(root->left, res);
            res.push_back(root->val);
            orderTravel(root->right, res);
        }
    }
};

5、深度优先搜索

...................
路径总和:给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
...................

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

    void dfs(TreeNode* root, int sum) 
    {
        if (root == nullptr) 
        {
            return;
        }
        path.push_back(root->val);
        sum -= root->val;
        if (root->left == nullptr && root->right == nullptr && sum == 0) 
        {
            res.push_back(path);
        }
        dfs(root->left, sum);
        dfs(root->right, sum);
        path.pop_back();
    }

    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        dfs(root, sum);
        return res;
    }
};

6、广度优先搜索

BFS一般都是用队列去做。BSF找到的路径一定是最短的。BFS也是有算法框架,代码模板的。

其中的set是Java中的哈希表,对应的就是C++中的unordered_map


二叉树的层次遍历其实也算一种广度优先算法。跟这个模板写的都差不多。

力扣111题:二叉树的最小深度

class Solution {
public:
    int minDepth(TreeNode *root) {
        if (root == nullptr) {
            return 0;
        }

        queue<pair<TreeNode *, int> > que;
        que.emplace(root, 1);
        while (!que.empty()) {
            TreeNode *node = que.front().first;
            int depth = que.front().second;
            que.pop();
            if (node->left == nullptr && node->right == nullptr) {
                return depth;
            }
            if (node->left != nullptr) {
                que.emplace(node->left, depth + 1);
            }
            if (node->right != nullptr) {
                que.emplace(node->right, depth + 1);
            }
        }

        return 0;
    }
};

力扣994:腐烂的橘子

class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int dirs[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
        int row = grid.size();
        int colum = grid[0].size();
        queue<pair<int,int>> q;
        int count = 0;
        int time = 0;
        for (int i=0;i<row;i++) {
            for (int j=0;j<colum;j++) {
                if (grid[i][j] == 1)
                    count++;
                else if (grid[i][j] == 2)
                    q.push({i,j});
            }
        }
        if (count == 0)
            return 0;
        while (!q.empty()) {
            time++;
            for (int j=q.size();j>0;j--) {
                auto cur = q.front();
                q.pop();
                for (int k=0;k<4;k++) {
                    int x = cur.first + dirs[k][0];
                    int y = cur.second + dirs[k][1];
                    if (x<0 || x>row-1 || y<0 || y>colum-1 || grid[x][y] == 0)
                        continue;
                    if (grid[x][y] == 1) {
                        count--;
                        grid[x][y] = 2;
                        q.push({x,y});
                    }
                }
                if (count == 0)
                    return time;
            }
        }
        return -1;
    }
};

力扣752:打开转盘锁

class Solution {
public:

    string plusOne(string str,int i)//数字+1
    {
        str[i] = str[i]=='9'?'0':str[i]+1;
        return str;
    }

    string downOne(string str,int i)//数字-1
    {
        str[i] = str[i]=='0'?'9':str[i]-1;
        return str;
    }


    int openLock(vector<string>& deadends, string target) {
        unordered_set<string>deadset(deadends.begin(),deadends.end());//死亡密码

        unordered_set<string>visited;//走过的密码
        visited.insert("0000");

        queue<string>q;//当前的密码
        q.push("0000");

        int step=0;

        while(!q.empty())
        {
            int size = q.size();//当前这一批次,广度优先遍历,一批(一层)一批的走
            for(int i=0;i<size;i++)
            {
                string cur = q.front();//这一批次,当前密码
                q.pop();

                if(deadset.count(cur)) continue; //在死亡密码中,换下一个
                if(cur==target) return step;    //到达目标,结束

                for(int i=0;i<4;i++)    //每次转动,有(上、下)*4 种可能
                {
                    string up = plusOne(cur,i);  //向上
                    if(!visited.count(up))       //没有走过
                    {
                        q.push(up);              //放入当前密码(下一批次计算)
                        visited.insert(up);      //放入走过密码
                    }
                    string down = downOne(cur,i); //向下
                    if(!visited.count(down))      //没有走过
                    {
                        q.push(down);
                        visited.insert(down);
                    }
                }
            }
            step++;
        }
        return -1;
    }
};

7、回溯法

回溯法和DFS其实是一样的。
回溯法主要注意路径,选择列表做选择,回溯递归,撤销选择
回溯法的代码模板

vector<vector<int> res;//储存所有的路径
vector<vector<int> DFS(vector<int> &nums)//主函数
{
  vector<int> track;
  backtrace(nums,track,……);
  return res;
}
void backtrack(vector<int> &nums,vector<int> &track)//回溯函数
{
  //排除不合法的选择


  if()//如果满足条件,更新res
  {
    res.push_back(track);
    return;
  }
  for()//开始循环
  {
    track.push_back();//做选择
    backtrack();//递归回溯
    track.pop_back();//撤销选择
  }
}

力扣46题:全排列

class Solution {
public:
    vector<vector<int>> permute(vector<int>& num) {
        vector<vector<int>> res;
        vector<int> out, visited(num.size(), 0);
        permuteDFS(num, visited, out, res);
        return res;
    }
    void permuteDFS(vector<int>& num, vector<int>& visited, vector<int>& out, vector<vector<int>>& res) {
        if (out.size() == num.size()) {res.push_back(out); return;}
        for (int i = 0; i < num.size(); ++i) {
            if (visited[i] == 1) continue;
            visited[i] = 1;
            out.push_back(num[i]);
            permuteDFS(num, visited, out, res);
            out.pop_back();
            visited[i] = 0;
        }
    }
};

力扣77题:组合

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> res;
        vector<int> out;
        helper(n, k, 1, out, res);
        return res;
    }
    void helper(int n, int k, int level, vector<int>& out, vector<vector<int>>& res) {
        if (out.size() == k) {res.push_back(out); return;}
        for (int i = level; i <= n; ++i) {
            out.push_back(i);
            helper(n, k, i + 1, out, res);
            out.pop_back();
        }
    }
};

力扣78:子集

class Solution {
public:
    vector<vector<int> > subsets(vector<int> &S) {
        vector<vector<int> > res;
        vector<int> out;
        sort(S.begin(), S.end());
        getSubsets(S, 0, out, res);
        return res;
    }
    void getSubsets(vector<int> &S, int pos, vector<int> &out, vector<vector<int> > &res) {
        res.push_back(out);
        for (int i = pos; i < S.size(); ++i) {
            out.push_back(S[i]);
            getSubsets(S, i + 1, out, res);
            out.pop_back();
        }
    }
};

8、动态规划

最重点的就是找状态转移方程
动态规划问题解题步骤:
1.确定base case
2.确定状态,也就是原问题和子问题中的变量。比如力扣凑零钱问题中的目标金额就是这题的状态
3.确定选择,也就是导致状态产生变化的行为。每次选择硬币就是在更改目标金额,这就是选择
4.确定dp函数或数组的定义
代码模板:

#初始化base case
dp[0][……][……] = base case;
#进行状态转移
for 状态1 in 状态1的所有取值:
  for 状态2 in 状态2的所有取值:
      状态n………………
        if()#做选择(排除子问题无解的情况,如果没有就不写这行话)
          dp[状态1][状态2] = 求最值(选择1,选择2,……);

关于子序列的问题的模板
子序列问题有两种定义dp数组的思路
第一种:
一维数组的dp:
含义是:在子数组nums[0……i]中,以nums[i]为结尾的(最长递增子序列的长度为dp[i],以nums[i]为结尾的最大子数组和为nums[i])
第二种:
二维数组的dp:
在涉及两个字符串或数组时含义是:在子数组nums1[0……i]和nums2[0……i]中,要求的子序列(最长公共子序列)长度为dp[i][j]
在只涉及一个字符串或数组时含义是:在子数组nums[0……i]中,要求的子序列(最长回文子序列)长度为dp[i][j](我比较少用这个)
力扣332题:零钱兑换

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount+1,amount+1);
        dp[0] = 0;
        for(int i=1;i<amount+1;i++)
        {
            for(int j=0;j<coins.size();j++)
            {
                if(i-coins[j] >= 0)
                    dp[i] = min(dp[i],dp[i-coins[j]]+1);
            }
        }
        return dp[amount]>amount?-1:dp[amount];
    }
};

力扣300:最长递增子序列

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size() == 0)
            return 0;
        if(nums.size() == 1)
            return 1;
        vector<int> dp(nums.size(),1);
        for(int i=0;i<nums.size();i++)
        {
            for(int j=0;j<i;j++)
            {
                if(nums[j] < nums[i])
                    dp[i] = max(dp[i],dp[j]+1);
            }
        }
        return *max_element(dp.begin(),dp.end());
    }
};

力扣1143题:最长公共子序列

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) 
    {
        int l1 = text1.size();
        int l2 = text2.size();
        vector<vector<int>> dp (l1+1,vector<int>(l2+1,0));
        int i, j;
        for(i = 0; i <= l1; i++){   //边界状态
            dp[i][0] = 0;
        }
        for(i = 0; i <= l2; i++){
            dp[0][i] = 0;
        }
        for(i = 1; i <= l1; i++){     //注意下标从1开始 
            for(j = 1; j <= l2; 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[l1][l2];
    }
};

9、分治法

10、二分法

二分法很简单

int binarySearch(vector<int> nums,int target)
{
  int left = 0;
  int right = nums.size()-1;
  while (left <= right)
  {
    int mid = left + (right - left)/2;//注意这里,和(right+left)/2一样,但是一定程度上可以防止溢出
    if(nums[mid] == target)
      return mid;
    if(nums[mid] < target)
      left = mid + 1;
    if(nums[mid] == target)
      right = mid - 1;
  }
  return -1;
}

11、贪心算法

12、其他一些小技巧和操作

迷失的人迷失了,相逢的人会再相逢