LeetCode题目答案及理解汇总(持续更新)

面试算法题

dfs相关

全排列

在这里插入图片描述
#include<bits/stdc++.h>

using namespace std;

const int N = 10;

//用一个path数组来存储每次到底层的路径 
int path[N];
//用一个布尔数组来存储每次已经遍历的点,默认是false 
bool st[N];
int n;

//u表示当前的层数 
void dfs(int u)
{
    //当已经到达最底层了,溯回并输出路径 
    if( u == n )
    {
        for(int i = 0 ; i < n ; i++) printf("%d " , path[i] );
        //作用跟printf("%s\n",s),默认帮你换行 
        puts("");
        //溯回上一层 
        return;
    }
    else
    {
        //这里从第一个数开始循环 
        for(int i = 1; i <= n ; i++)
        {
            //如果该数字未被访问,就使用 
            if( !st[i] )
            {
                path[u] = i;
                //标记第i个数已经被使用 
                st[i] = true;
                //进入下一层 
                dfs( u + 1 );
                //还原现场 
                st[i] = false; 
            }
        }
    }

}

int main()
{
    cin >> n;

    dfs(0);

    return 0;
}

n皇后问题


//y总第一个解法,按列来枚举`
#include<bits/stdc++.h>

using namespace std;

const int N = 20;

char g[N][N];
bool col[N] , dg[N] , udg[N];
int n;

void dfs(int u)
{
    if( u == n )
    {
        for(int i = 0 ; i < n ; i++) puts( g[i] );

        puts("");

        return;
    }
    else
    {
        for(int i = 0 ; i < n ; i++)
        {
            //当满足列没有皇后,对角线没有皇后,反对角线没有皇后
            if( !col[i] && !dg[u + i] && !udg[n - i + u] )
            {
                //这一格子放置皇后
                g[u][i] = 'Q';
                //此时这一列,这一对角线,这一反对角线就不能放置皇后了
                col[i] = dg[u + i] = udg[n - i + u] = true;
               //递归到下一层 
                dfs(u + 1);
                //递归出来后返回上一层,并还原现场
                col[i] = dg[u + i] = udg[n - i + u] = false;
                //把皇后给做掉
                g[u][i] = '.';
            }
        }
    }
}

int main()
{
    cin >> n;

    for(int i = 0 ; i < n ; i ++)
    {
        for(int j = 0 ; j < n; j++) g[i][j] = '.';
    }

    dfs(0);

    return 0;
}
//第二种方法,遍历每一个格子
#include<bits/stdc++.h>

using namespace std;

const int N = 20;

char g[N][N];
bool row[N] , col[N] , dg[N] , udg[N];
int n;

//表示第x行第y列,放置了s个皇后
void dfs(int x , int y , int s)
{
    //当当前x行已经到达边界,转到下一行,列数归零
    if( y == n ) y = 0 , x++;

    //当到最后一个行,如果此时已经存在了n个皇后,就输出结果
    //为什么不判断y呢?因为最后一行只能放一个皇后
    if( x == n )
    {
        if( s == n )
        {
            for(int i = 0 ; i < n ; i++) puts( g[i] );
            puts("");
        }

        return;
    }


    //放皇后
    if( !row[x] && !col[y] && !dg[y + x] && !udg[y - x + n] )
    {
        g[x][y] = 'Q';

        row[x] = col[y] = dg[x + y] = udg[y - x + n] = true;

        dfs(x , y + 1 , s + 1 );

        row[x] = col[y] = dg[x + y] = udg[y - x + n] = false;

        g[x][y] = '.';
    }

    //不放置皇后
    dfs( x , y + 1 , s );
}

int main()
{
    cin >> n;

    for(int i = 0 ; i < n ; i ++)
    {
        for(int j = 0 ; j < n; j++) g[i][j] = '.';
    }

    dfs(0 , 0 , 0);

    return 0;
}

子集

解题思路:
1.DFS 和回溯算法区别
DFS 是一个劲的往某一个方向搜索,而回溯算法建立在 DFS 基础之上的,但不同的是在搜索过程中,达到结束条件后,恢复状态,回溯上一层,再次搜索。因此回溯算法与 DFS 的区别就是有无状态重置

2.何时使用回溯算法
当问题需要 "回头",以此来查找出所有的解的时候,使用回溯算法。即满足结束条件或者发现不是正确路径的时候(走不通),要撤销选择,回退到上一个状态,继续尝试,直到找出所有解为止

3.怎么样写回溯算法(从上而下,※代表难点,根据题目而变化)
①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
②根据题意,确立结束条件
③找准选择列表(与函数参数相关),与第一步紧密关联※
④判断是否需要剪枝
⑤作出选择,递归调用,进入下一层
⑥撤销选择
4.回溯问题的类型
这里先给出,我总结的回溯问题类型,并给出相应的 leetcode题目(一直更新),然后再说如何去编写。特别关注搜索类型的,搜索类的搞懂,你就真的搞懂回溯算法了,是前面两类是基础,帮助你培养思维

类型 题目链接
子集、组合:子集、子集 II、组合、组合总和、组合总和 II
全排列:全排列、全排列 II、字符串的全排列、字母大小写全排列
搜索:解数独、单词搜索、N皇后、分割回文串、二进制手表
注意:子集、组合与排列是不同性质的概念。子集、组合是无关顺序的,而排列是和元素顺序有关的,如 [1,2] 和 [2,1] 是同一个组合(子集),但 [1,2] 和 [2,1] 是两种不一样的排列!!!!因此被分为两类问题

5.回到子集、组合类型问题上来(ABC 三道例题)
A、 子集 - 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
解题步骤如下

①递归树

观察上图可得,选择列表里的数,都是选择路径(红色框)后面的数,比如[1]这条路径,他后面的选择列表只有"2、3",[2]这条路径后面只有"3"这个选择,那么这个时候,就应该使用一个参数start,来标识当前的选择列表的起始位置。也就是标识每一层的状态,因此被形象的称为"状态变量",最终函数签名如下

C++

//nums为题目中的给的数组
//path为路径结果,要把每一条 path 加入结果集
void backtrack(vector<int>nums,vector<int>&path,int start)

②找结束条件
此题非常特殊,所有路径都应该加入结果集,所以不存在结束条件。或者说当 start 参数越过数组边界的时候,程序就自己跳过下一层递归了,因此不需要手写结束条件,直接加入结果集

C++

**res为结果集,是全局变量vector<vector<int>>res,到时候要返回的
res.push_back(path);//把每一条路径加入结果集

③找选择列表
在①中已经提到过了,子集问题的选择列表,是上一条选择路径之后的数,即

C++

for(int i=start;i<nums.size();i++)

④判断是否需要剪枝
从递归树中看到,路径没有重复的,也没有不符合条件的,所以不需要剪枝

⑤做出选择(即for 循环里面的)
C++

void backtrack(vector<int>nums,vector<int>&path,int start)
{
    for(int i=start;i<nums.size();i++)
    {
        path.push_back(nums[i]);//做出选择
        backtrack(nums,path,i+1);//递归进入下一层,注意i+1,标识下一个选择列表的开始位置,最重要的一步
    }
}

⑤撤销选择
整体的 backtrack 函数如下

C++

class Solution {
public:
vector<int> path;
vector<vector<int>> res;
    void backtrack(vector<int>nums,vector<int>path,int start)
{
    res.push_back(path);
    for(int i=start;i<nums.size();i++)
    {
        path.push_back(nums[i]);//做出选择
        backtrack(nums,path,i+1);//递归进入下一层,注意i+1,标识下一个选择列表的开始位置,最重要的一步
        path.pop_back();//撤销选择
    }
}

    vector<vector<int>> subsets(vector<int>& nums) {
        backtrack(nums,path,0);
        return res;
    }
};

完整代码:

class Solution {
public:
    vector<int> t;
    vector<vector<int>> ans;

    void dfs(int cur, vector<int>& nums) {
        if (cur == nums.size()) {
            ans.push_back(t);
            return;
        }
        t.push_back(nums[cur]);
        dfs(cur + 1, nums);
        t.pop_back();
        dfs(cur + 1, nums);
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        dfs(0, nums);
        return ans;
    }
};

B、子集 II(剪枝思想)--问题描述:
给定一个可能 包含重复元素 的整数数组 nums,返回该数组所有可能的子集(幂集)。
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

解题步骤
①递归树

可以发现,树中出现了大量重复的集合,②和③和第一个问题一样,不再赘述,我们直接看第四步

④判断是否需要剪枝,需要先对数组排序,使用排序函数 sort(nums.begin(),nums.end())
显然我们需要去除重复的集合,即需要剪枝,把递归树上的某些分支剪掉。那么应去除哪些分支呢?又该如何编码呢?

观察上图不难发现,应该去除当前选择列表中,与上一个数重复的那个数,引出的分支,如 “2,2” 这个选择列表,第二个 “2” 是最后重复的,应该去除这个 “2” 引出的分支

(去除图中红色大框中的分支)

编码呢,刚刚说到是 “去除当前选择列表中,与上一个数重复的那个数,引出的分支”,说明当前列表最少有两个数,当i>start时,做选择的之前,比较一下当前数,与上一个数 (i-1) 是不是相同,相同则 continue,

C++

void backtrack(vector<int>& nums,vector<int>&path,int start)
    {
        res.push_back(path);
        for(int i=start;i<nums.size();i++)
        {
            if(i>start&&nums[i]==nums[i-1])//剪枝去重
                continue;
        }
    }

⑤做出选择
C++

void backtrack(vector<int>& nums,vector<int>&path,int start)
    {
        res.push_back(path);
        for(int i=start;i<nums.size();i++)
        {
            if(i>start&&nums[i]==nums[i-1])//剪枝去重
                continue;
            temp.push_back(nums[i]);
            backtrack(nums,path,i+1);
        }
    }

⑥撤销选择
整体的backtrack函数如下

C++

** sort(nums.begin(),nums.end());
void backtrack(vector<int>& nums,vector<int>&path,int start)
    {
        res.push_back(path);
        for(int i=start;i<nums.size();i++)
        {
            if(i>start&&nums[i]==nums[i-1])//剪枝去重
                continue;
            temp.push_back(nums[i]);
            backtrack(nums,path,i+1);
            temp.pop_back();
        }
    }

完整代码:

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {
        result.push_back(path);
        for (int i = startIndex; i < nums.size(); i++) {
            // used[i - 1] == true,说明同一树支candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            // 而我们要对同一树层使用过的元素进行跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            path.push_back(nums[i]);
            used[i] = true;
            backtracking(nums, i + 1, used);
            used[i] = false;
            path.pop_back();
        }
    }

public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool> used(nums.size(), false);
        sort(nums.begin(), nums.end()); // 去重需要排序
        backtracking(nums, 0, used);
        return result;
    }
};

C、组合总和 - 问题描述
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
输入: candidates = [1,2,3], target = 3,
所求解集为:
[
[1,1,1],
[1,2],
[3]
]

解题步骤
①递归树

(绿色箭头上面的是路径,红色框[]则为结果,黄色框为选择列表)
从上图看出,组合问题和子集问题一样,1,2 和 2,1 `是同一个组合,因此 需要引入start参数标识,每个状态中选择列表的起始位置。另外,每个状态还需要一个 sum 变量,来记录当前路径的和,函数签名如下

C++

void backtrack(vector& nums,vector&path,int start,int sum,int target)
②找结束条件
由题意可得,当路径总和等于 target 时候,就应该把路径加入结果集,并 return

C++

if(target==sum)
{
res.push_back(path);
return;
}
③找选择列表
C++

for(int i=start;i<nums.size();i++)
④判断是否需要剪枝
从①中的递归树中发现,当前状态的sum大于target的时候就应该剪枝,不用再递归下去了

C++

for(int i=start;i<nums.size();i++)
{
if(sum>target)//剪枝
continue;
}
⑤做出选择
题中说数可以无限次被选择,那么 i 就不用 +1 。即下一层的选择列表,从自身开始。并且要更新当前状态的sum

C++

for(int i=start;i<nums.size();i++)
{
if(sum>target)
continue;
path.push_back(nums[i]);
backtrack(nums,path,i,sum+nums[i],target);//i不用+1(重复利用),并更新当前状态的sum
}
⑤撤销选择
整体的 backtrack 函数如下

C++

void backtrack(vector& nums,vector&path,int start,int sum,int target)
{
for(int i=start;i<nums.size();i++)
{
if(sum>target)
continue;
path.push_back(nums[i]);
backtrack(nums,path,i,sum+nums[i],target);//更新i和当前状态的sum
pacht.pop_back();
}
}
总结:子集、组合类问题,关键是用一个 start 参数来控制选择列表!!最后回溯六步走:
①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
②根据题意,确立结束条件
③找准选择列表(与函数参数相关),与第一步紧密关联※
④判断是否需要剪枝
⑤作出选择,递归调用,进入下一层
⑥撤销选择

CodeTope测试

无重复字符的最长字串

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        string str;
        int i,j,max=0,n=s.length();
        for(i=0;i<n;i++){
            for(j=i;j<n;j++){
                if(str.find(s[j])==string::npos)str=str+s[j];
                else break;
            }
            max=(max>str.length())?max:str.length();
            str="";
        }
        return max;        
    }
};

有效的括号

class Solution {
public:
    bool isValid(string s) {
        int n = s.size();
        if (n % 2 == 1) {
            return false;
        }
        unordered_map<char, char> pairs = {
            {')', '('},
            {']', '['},
            {'}', '{'}
        };
        stack<char> stk;
        for (char ch: s) {
            if (pairs.count(ch)) {
                if (stk.empty() || stk.top() != pairs[ch]) {
                    return false;
                }
                stk.pop();
            }
            else {
                stk.push(ch);
            }
        }
        return stk.empty();
    }
};

两数之和

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int i=0,j;
        vector<int> ans;
        for(i=0;i<nums.size();i++){
            for(j=i+1;j<nums.size();j++){
                 if(nums[i]+nums[j]==target){
                     ans.push_back(i);
                     ans.push_back(j);
                     break;
                 }
            }        
        }
        return ans;
    }
};

(自己写的)

反转链表(迭代)

public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode curr = head;
    while (curr != null) {
        ListNode next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}

最长回文

class Solution {
public:
    string longestPalindrome(string s) {
        int n=s.size();
        string ans;
        int i=0,j=0,len=0;
        for(i=0;i<=n;i++){

            for(j=0;j<=i;j++){
                if(s[i-j]==s[i+j]&&(j*2+1)>len){
                    len=j*2+1;
                    ans=s.substr(i-j,i+j+1);
                }
            }
            for(j=0;j<=i;j++){
                if(s[i-j]==s[i+j+1]&&(j*2+2)>len){
                    len=j*2+2;
                    ans=s.substr(i-j,i+j+2);
                }
            }
        }
        return ans;
    }
};

为什么不对???

class Solution {
public:
    string longestPalindrome(string s) {
        int len=s.size();
        if(len==0||len==1)
            return s;
        int start=0;//记录回文子串起始位置
        int end=0;//记录回文子串终止位置
        int mlen=0;//记录最大回文子串的长度
        for(int i=0;i<len;i++)
        {
            int len1=expendaroundcenter(s,i,i);//一个元素为中心
            int len2=expendaroundcenter(s,i,i+1);//两个元素为中心
            mlen=max(max(len1,len2),mlen);
            if(mlen>end-start+1)
            {
                start=i-(mlen-1)/2;
                end=i+mlen/2;
            }
        }
        return s.substr(start,mlen);
        //该函数的意思是获取从start开始长度为mlen长度的字符串
    }
private:
    int expendaroundcenter(string s,int left,int right)
    //计算以left和right为中心的回文串长度
    {
        int L=left;
        int R=right;
        while(L>=0 && R<s.length() && s[R]==s[L])
        {
            L--;
            R++;
        }
        return R-L-1;
    }
};

环形链表

快慢指针

class Solution {
public:
    bool hasCycle(ListNode* head) {
        if (head == nullptr || head->next == nullptr) {
            return false;
        }
        ListNode* slow = head;
        ListNode* fast = head->next;
        while (slow != fast) {
            if (fast == nullptr || fast->next == nullptr) {
                return false;
            }
            slow = slow->next;
            fast = fast->next->next;	//总有一天能追上
        }
        return true;
    }
};

哈希表

class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> seen;
        while (head != nullptr) {
            if (seen.count(head)) {
                return true;
            }
            seen.insert(head);
            head = head->next;
        }
        return false;
    }
};

猜数字游戏

class Solution {
public:
    string getHint(string secret, string guess) {
        int bulls = 0;
        vector<int> cntS(10), cntG(10);
        for (int i = 0; i < secret.length(); ++i) {
            if (secret[i] == guess[i]) {
                ++bulls;
            } else {
                ++cntS[secret[i] - '0'];
                ++cntG[guess[i] - '0'];//出现个数
            }
        }
        int cows = 0;
        for (int i = 0; i < 10; ++i) {
            cows += min(cntS[i], cntG[i]);//最小出现次数
        }
        return to_string(bulls) + "A" + to_string(cows) + "B";
    }
};

最大子数组和

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int pre = 0, maxAns = nums[0];
        for (const auto &x: nums) {
            pre = max(pre + x, x);
            maxAns = max(maxAns, pre);
        }
        return maxAns;
    }
};

合并两个有序数组

偷跑方法

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        for (int i = 0; i != n; ++i) {
            nums1[m + i] = nums2[i];
        }
        sort(nums1.begin(), nums1.end());
    }
};

正常方法

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int p1 = 0, p2 = 0;
        int sorted[m + n];
        int cur;
        while (p1 < m || p2 < n) {
            if (p1 == m) {
                cur = nums2[p2++];
            } else if (p2 == n) {
                cur = nums1[p1++];
            } else if (nums1[p1] < nums2[p2]) {
                cur = nums1[p1++];
            } else {
                cur = nums2[p2++];
            }
            sorted[p1 + p2 - 1] = cur;
        }
        for (int i = 0; i != m + n; ++i) {
            nums1[i] = sorted[i];
        }
    }
};

排序数组(快排)

冒泡排序

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        // bubbleSort
        int n = nums.size();
        for (int i = 0; i < n - 1; ++i) {
            bool flag = false;
            for (int j = 0; j < n - 1 - i; ++j) {
                if (nums[j] > nums[j + 1]) {
                    swap(nums[j], nums[j + 1]);
                    flag = true;
                }                 
            }
            if (flag == false) break; //无交换,代表当前序列已经最优 
        }
        return nums;
    }
};

三数之和

https://leetcode.cn/problems/3sum/

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        vector<vector<int>> ans;
        // 枚举 a
        for (int first = 0; first < n; ++first) {
            // 需要和上一次枚举的数不相同
            if (first > 0 && nums[first] == nums[first - 1]) {
                continue;
            }
            // c 对应的指针初始指向数组的最右端
            int third = n - 1;
            int target = -nums[first];
            // 枚举 b
            for (int second = first + 1; second < n; ++second) {
                // 需要和上一次枚举的数不相同
                if (second > first + 1 && nums[second] == nums[second - 1]) {
                    continue;
                }
                // 需要保证 b 的指针在 c 的指针的左侧
                while (second < third && nums[second] + nums[third] > target) {
                    --third;
                }
                // 如果指针重合,随着 b 后续的增加
                // 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                if (second == third) {
                    break;
                }
                if (nums[second] + nums[third] == target) {
                    ans.push_back({nums[first], nums[second], nums[third]});
                }
            }
        }
        return ans;
    }
};

看懂了,但自己写总是没思路,真难受。

大致思路:定义三个指针a,b,c隐含关系:a<b<c

先排序

循环a++,若nums[a]>0则不可能sum=0

c在最右即最大数

b=a+1;b++

c+b>target,需要变小,则c--;c+b<target,需要变大,则b++;

最长公共前缀

https://leetcode.cn/problems/longest-common-prefix/

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""。

示例 1:

输入:strs = ["flower","flow","flight"]
输出:"fl"
示例 2:

输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        if (!strs.size()) {
            return "";
        }
        int length = strs[0].size();
        int count = strs.size();
        for (int i = 0; i < length; ++i) {
            char c = strs[0][i];
            for (int j = 1; j < count; ++j) {
                if (i == strs[j].size() || strs[j][i] != c) {
                    return strs[0].substr(0, i);
                }
            }
        }
        return strs[0];
    }
};

注意.substring(str.begin(),str.end())和.substr(str.begin(),length)的区别

思路还是比较简单的:每次查询第一个串的一个字符,是否与其他串相同。

二分查找

https://leetcode.cn/problems/binary-search/

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

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left=0,right=nums.size()-1,mid=0;
        while(left<=right){
            mid=(left+right)/2;
            if(nums[mid]==target){
                return mid;
            }
            else if(nums[mid]<target)left=mid+1;
            else right=mid-1;
        }
        return -1;
    }
};

这题就不多说了,没什么难度。

多数元素

https://leetcode.cn/problems/majority-element/

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入:nums = [3,2,3]
输出:3
示例 2:

输入:nums = [2,2,1,1,1,2,2]
输出:2

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int n=nums.size();
        return nums[n/2];
    }
};

这题也很简单,第一次有了看到题就有完整思路的感觉

当时也想到了暴力搜索,出现次数超过n/2就是多数。

买股票的最佳时机

https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int inf = 1e9;
        int minprice = inf, maxprofit = 0;
        for (int price: prices) {
            maxprofit = max(maxprofit, price - minprice);
            minprice = min(price, minprice);
        }
        return maxprofit;
    }
};

最开始用暴力,结果时间上没过。

这种方法只遍历一次,比如我打算在第n天卖出,则最大利润肯定是,在(0,n)中选择历史最低点买入。因此只需要一个变量记录并更新历史最低点即可。

相交链表

https://leetcode.cn/problems/intersection-of-two-linked-lists/

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交:

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *pa = headA;
        ListNode *pb = headB;
        while(pa!=pb){
            pa=pa==NULL?headB:pa->next;
            pb=pb==NULL?headA:pb->next;
        }
        return pa;
    }
};

这是一种巧妙解法:

原理是:指针A、B同时开始,若路程不同,则短的一方(假设为B)会指向长的一方A起始,此时再过gap(差距)A也遇到NULL跳转到B起始,神奇的事情发生了,两者同步了:与NULL的距离相同了,齐头并进,若有相交,AB必定相遇。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode *> visited;
        ListNode *temp = headA;
        while (temp != nullptr) {
            visited.insert(temp);
            temp = temp->next;
        }
        temp = headB;
        while (temp != nullptr) {
            if (visited.count(temp)) {
                return temp;
            }
            temp = temp->next;
        }
        return nullptr;
    }
};

这是暴力且直接的哈希表解法

先记录一条链上所有,然后第二条对比。

环形链表II

https://leetcode.cn/problems/linked-list-cycle-ii/

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

img

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode*> seen;
        while (head != nullptr) {
            if (seen.count(head)) {
                return head;
            }
            seen.insert(head);
            head = head->next;
        }
        return 0;
    }
};

好像做过,直接用哈希表

环形链表

https://leetcode.cn/problems/linked-list-cycle/

class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> seen;
        while (head != nullptr) {
            if (seen.count(head)) {
                return true;
            }
            seen.insert(head);
            head = head->next;
        }
        return false;
    }
};

就是这个

验证IP地址

https://leetcode.cn/problems/validate-ip-address/

给定一个字符串 queryIP。如果是有效的 IPv4 地址,返回 "IPv4" ;如果是有效的 IPv6 地址,返回 "IPv6" ;如果不是上述类型的 IP 地址,返回 "Neither" 。

有效的IPv4地址 是 “x1.x2.x3.x4” 形式的IP地址。 其中 0 <= xi <= 255 且 xi 不能包含 前导零。例如: “192.168.1.1” 、 “192.168.1.0” 为有效IPv4地址, “192.168.01.1” 为无效IPv4地址; “192.168.1.00” 、 “192.168@1.1” 为无效IPv4地址。

class Solution {
public:
    string validIPAddress(string queryIP) {
        if (queryIP.find('.') != string::npos) {
            // IPv4
            int last = -1;
            for (int i = 0; i < 4; ++i) {
                int cur = (i == 3 ? queryIP.size() : queryIP.find('.', last + 1));
                if (cur == string::npos) {
                    return "Neither";
                }
                if (cur - last - 1 < 1 || cur - last - 1 > 3) {
                    return "Neither";
                }
                int addr = 0;
                for (int j = last + 1; j < cur; ++j) {
                    if (!isdigit(queryIP[j])) {
                        return "Neither";
                    }
                    addr = addr * 10 + (queryIP[j] - '0');
                }
                if (addr > 255) {
                    return "Neither";
                }
                if (addr > 0 && queryIP[last + 1] == '0') {
                    return "Neither";
                }
                if (addr == 0 && cur - last - 1 > 1) {
                    return "Neither";
                }
                last = cur;
            }
            return "IPv4";
        }
        else {
            // IPv6
            int last = -1;
            for (int i = 0; i < 8; ++i) {
                int cur = (i == 7 ? queryIP.size() : queryIP.find(':', last + 1));
                if (cur == string::npos) {
                    return "Neither";
                }
                if (cur - last - 1 < 1 || cur - last - 1 > 4) {
                    return "Neither";
                }
                for (int j = last + 1; j < cur; ++j) {
                    if (!isdigit(queryIP[j]) && !('a' <= tolower(queryIP[j]) && tolower(queryIP[j]) <= 'f')) {
                        return "Neither";
                    }
                }
                last = cur;
            }
            return "IPv6";
        }
    }
};

额,看的题解,还是昏的。

正则看起来简洁,但表达式同样复杂。

用栈实现堆操作

https://leetcode.cn/problems/implement-queue-using-stacks/

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:

void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false

class MyQueue {
private:
    stack<int> inStack, outStack;

    void in2out() {
        while (!inStack.empty()) {
            outStack.push(inStack.top());
            inStack.pop();
        }
    }

public:
    MyQueue() {}

    void push(int x) {
        inStack.push(x);
    }

    int pop() {
        if (outStack.empty()) {
            in2out();
        }
        int x = outStack.top();
        outStack.pop();
        return x;
    }

    int peek() {
        if (outStack.empty()) {
            in2out();
        }
        return outStack.top();
    }

    bool empty() {
        return inStack.empty() && outStack.empty();
    }
};

重点就在于取出最开始入栈元素。用另一个栈调一下方向即可,因为这里,即使后续又push,但pop永远会先把另一个栈中元素取完。

爬楼梯

https://leetcode.cn/problems/climbing-stairs/

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 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;
        }
        return r;
    }
};

这里要分析一下爬楼梯的规律

1层:1;2层:11,2;3层:111,21,12;4层:1111,121,211,22,112是一个斐波那契数列,要到达n层:n-1层+1||n-2层+2

合并两个有序链表

https://leetcode.cn/problems/merge-two-sorted-lists/

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

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2){
        if (list1 == nullptr) {
            return list2;
        } else if (list2 == nullptr) {
            return list1;
        } else if (list1->val < list2->val) {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        } else {
            list2->next = mergeTwoLists(list1, list2->next);
            return list2;
        }
    }
};

比较巧妙的递归,我反正想不出来。。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* preHead = new ListNode(-1);

        ListNode* prev = preHead;
        while (l1 != nullptr && l2 != nullptr) {
            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;
    }
};

我一开始思路就是这样,但跑不起来,少了ListNode* preHead = new ListNode(-1);所以返回的链表都少了表头

2022·9·10

感觉篇幅有点长了,所以还是用时间分割一下

数组中第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5
示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        sort(nums.begin(),nums.end());
        int i,n=nums.size();
        for(i=n-1;;i--){
            k--;
            if(k==0)return nums[i];
        }
        return 0;
    }
};

看中等题,还以为用sort会超时,没想到竟然能通。。

看了一下官方解法:用一个大小为K的堆,每次读一个数,插入堆,溢出则弹出最小的那个

全排列

https://leetcode.cn/problems/permutations/

在dfs中已经做过了,这里不多说

删除排序链表中重复元素

https://leetcode.cn/problems/remove-duplicates-from-sorted-list/

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if (!head) {
            return head;
        }

        ListNode* ans = head;
        while (ans->next) {
            if (ans->val == ans->next->val)ans->next = ans->next->next;
            else ans = ans->next;
        }
        return head;
    }
};

第一次做,思路都对,结果却不通过。看了解答才发现,应该返回head,因为ans会指向->null的结点。。属实是傻了。

最大数

https://leetcode.cn/problems/largest-number/

给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。

注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。

示例 1:

输入:nums = [10,2]
输出:"210"

class Solution {
private:
    static bool cmp(const int& a, const int& b) {
        string sa = to_string(a);
        string sb = to_string(b);
        return sa + sb > sb + sa;
    }
public:
    string largestNumber(vector<int>& nums) {
        string ans;
        sort(nums.begin(), nums.end(), cmp);
        for (int& t : nums) {
            ans += to_string(t);
        }
        if (ans[0] == '0') {  // 不能是00000
            return "0";
        }
        return ans;
    }
};

这题如果知道了sort()的用法的话会简单很多。sort(nums.begin(), nums.end(), cmp)结束后,基本就是输出了。

官方解答没有直接用字符相加,更像是对阶,将两数都变成同位,相加作比。

只出现一次的数字

https://leetcode.cn/problems/single-number/

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

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int i,n=nums.size(),same;
        if(n==1)return nums[0];
        for(i=0;i<n;i++){
            if(nums[i]==nums[i+1]){
                same=nums[i];
            }
            if(nums[i]==same)continue;
            else return nums[i];
        }
        return 0;
    }
};

要考虑数组只有一个或零个的可能

字符串转换整数

https://leetcode.cn/problems/string-to-integer-atoi/

class Automaton {
    string state = "start";
    unordered_map<string, vector<string>> table = {
        {"start", {"start", "signed", "in_number", "end"}},
        {"signed", {"end", "end", "in_number", "end"}},
        {"in_number", {"end", "end", "in_number", "end"}},
        {"end", {"end", "end", "end", "end"}}
    };

    int get_col(char c) {
        if (isspace(c)) return 0;
        if (c == '+' or c == '-') return 1;
        if (isdigit(c)) return 2;
        return 3;
    }
public:
    int sign = 1;
    long long ans = 0;

    void get(char c) {
        state = table[state][get_col(c)];
        if (state == "in_number") {
            ans = ans * 10 + c - '0';
            ans = sign == 1 ? min(ans, (long long)INT_MAX) : min(ans, -(long long)INT_MIN);
        }
        else if (state == "signed")
            sign = c == '+' ? 1 : -1;
    }
};

class Solution {
public:
    int myAtoi(string str) {
        Automaton automaton;
        for (char c : str)
            automaton.get(c);
        return automaton.sign * automaton.ans;
    }
};

正则还行,这自动机真不会。

自动机题解

https://leetcode.cn/problems/string-to-integer-atoi/solution/zi-fu-chuan-zhuan-huan-zheng-shu-atoi-by-leetcode-/

2022·9·11

二叉树的中序遍历

https://leetcode.cn/problems/binary-tree-inorder-traversal/

给定一个二叉树的根节点 root ,返回 它的 中序 遍历

class Solution {
public:
    void dfs(TreeNode* root,vector<int>& res){
        if(root==NULL){return;}
        dfs(root->left,res);
        res.push_back(root->val);
        dfs(root->right,res);
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        dfs(root,res);
        return res;
    }
};

自己做,但返回结果都是空,我寻思思路也没错。看题解,结果是void dfs(TreeNode* root,vector<int>& res)vector后没加&

二叉树的层序遍历

https://leetcode.cn/problems/binary-tree-level-order-traversal/

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

img

示例 1:

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector <vector <int>> ret;
        if (!root) {
            return ret;
        }
        queue <TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            int currentLevelSize = q.size();
            ret.push_back(vector <int> ());
            for (int i = 1; i <= currentLevelSize; ++i) {
                auto node = q.front(); q.pop();
                ret.back().push_back(node->val);
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
        }    
        return ret;
    }
};

思路和上一道题类似,这里就不展开了

滑动窗口最大值

https://leetcode.cn/problems/sliding-window-maximum/

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值


[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        priority_queue<pair<int, int>> q;
        for (int i = 0; i < k; ++i) {
            q.emplace(nums[i], i);
        }
        vector<int> ans = {q.top().first};
        for (int i = k; i < n; ++i) {
            q.emplace(nums[i], i);
            while (q.top().second <= i - k) {
                q.pop();
            }
            ans.push_back(q.top().first);
        }
        return ans;
    }
};

知道用队列,但忘接口方法了。

优先队列详解:https://www.cnblogs.com/huashanqingzhu/p/11040390.html

字符串相加

https://leetcode.cn/problems/add-strings/

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。

你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。

示例 1:

输入:num1 = "11", num2 = "123"
输出:"134"

class Solution {
public:
    string addStrings(string num1, string num2) {
        int i = num1.length() - 1, j = num2.length() - 1, add = 0;
        string ans = "";
        while (i >= 0 || j >= 0 || add != 0) {
            int x = i >= 0 ? num1[i] - '0' : 0;
            int y = j >= 0 ? num2[j] - '0' : 0;
            int result = x + y + add;
            ans.push_back('0' + result % 10);
            add = result / 10;
            i -= 1;
            j -= 1;
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

扑克牌中顺子

https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/

从若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。

示例 1:

输入: [1,2,3,4,5]
输出: True

class Solution {
public:
    bool isStraight(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int boss = 0;
        for(;boss<nums.size();++boss){
            if(nums[boss]!=0)
                break;
        }
        int pre = nums[boss];
        int count=0;
        for(int i=boss+1;i<nums.size();++i){
            if(nums[i] != pre+1){
                if(nums[i] == pre){
                    return false;
                }
                count = count + nums[i] - pre - 1;
            }
            pre = nums[i];
        }
        if(count > boss)
            return false;
        return true;
    }
};

模拟:1.模拟手上清牌,可以用排序,这样的话大小王作为0就排在前面,后面的都是正整数牌
2.清点boss牌,有多少张boss牌,这里由于是在多幅牌当中,甚至可能出现五张王
3.对不是boss牌的第一张进行记录,第一种情况:后面一张牌如果是恰好比前一张牌大1,说明是连着的,中间的差值不用记录,因为差值为0。第二种情况:如果后一张牌和前一张牌相等,说明肯定不是顺子,直接返回false。第三种情况:后一张牌比前一张牌大,且大超过1,说明有可能是顺子,这里的话先记录下count,也就是到时候需要用几张boss牌来填充。
4.如果需要的万能牌个数count是大于实际手上的boss牌,那么说明boss牌不够,返回false。如果这个缺口count比较小,可以利用boss牌来填充,返回true。

K个一组翻转链表

https://leetcode.cn/problems/reverse-nodes-in-k-group/

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        Deque<ListNode> stack = new ArrayDeque<ListNode>();
        ListNode dummy = new ListNode(0);
        ListNode p = dummy;
        while (true) {
            int count = 0;
            ListNode tmp = head;
            while (tmp != null && count < k) {
                stack.add(tmp);
                tmp = tmp.next;
                count++;
            }
            if (count != k) {
                p.next = head;
                break;
            }
            while (!stack.isEmpty()){
                p.next = stack.pollLast();
                p = p.next;
            }
            p.next = tmp;
            head = tmp;
        }
        return dummy.next;
    }
}

当时没想明白,之后有时间,对着Java写C++的。

2022·9·13

找到实习了,暂不更新

posted @   海之龙  阅读(962)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示