为了能到远方,脚下的每一步都|

洛谷熟悉语法(C++)

数组

过河卒问题

该题链接如下https://www.luogu.com.cn/problem/P1002,是一道很基础的动态规划题,观察题目要求,内存限制125MB,而时间限制为1.00s。

思路一

使用递归,即f(i,j)=f(i1,j)+f(j1,i),同时注意判断可以被马吃的地方有f(i,j)=0。具体代码如下:

//https://www.luogu.com.cn/problem/P1002
//看题解思路:使用递归(x) 使用递归复杂度约是2^(n+m) 必爆掉
#include<bits/stdc++.h>
using namespace std;

long long int path_sum(int x,int y,int h_x,int h_y);
bool is_horse(int x,int y,int h_x,int h_y);

int main(){
    int p_x=0,p_y=0,h_x=0,h_y=0;
    cin >> p_x >> p_y >> h_x >> h_y;
    // cout << p_x << p_y << h_x << h_y;
    cout << path_sum(p_x,p_y,h_x,h_y);
    return 0;
}

long long int path_sum(int x,int y,int h_x,int h_y){
    if( is_horse(x,y,h_x,h_y)){
        return 0;
    }else if(x==0 || y==0){
        return 1;
    }else{
        return path_sum(x-1,y,h_x,h_y) + path_sum(x,y-1,h_x,h_y);
    }
}
bool is_horse(int x,int y,int h_x,int h_y){
    int abx = ((x - h_x) < 0) ? (h_x - x) : (x - h_x);
    int aby = ((y - h_y) < 0) ? (h_y - y) : (y - h_y);
    if(abx == 1){
        return (aby==2) ? true : false;
    }else if(abx == 2){
        return (aby==1) ? true : false;
    }
    return (abx == 0) && (aby == 0) ;
}

这样提交上去会有TLE,简单看其复杂度,为O(2n+m)。故转用动态规划。

思路二

使用动态规划,然鹅动态规划基本不会了,参考博客https://zhuanlan.zhihu.com/p/365698607,手动感谢大佬orz。

简单来说,动态规划采用自底向上的方法,避免了递归时大量重复计算的问题(本题中,可以想象,f(0,1),f(1,1),f(1,0)这些,在递归时都被重复计算很多变)

一个朴素的想法就是:自底向上,把先计算的计算结果存起来,利用递推关系,就可以很快的计算出最终结果。使用空间换取时间。

本题,可以建一个二维数组,从(0,0)开始,一直算到(n,m),时间复杂度为O(nm)。代码如下

//https://www.luogu.com.cn/problem/P1002
#include<bits/stdc++.h>
using namespace std;
#define N 25

bool is_horse(int x ,int y,int h_x,int h_y);


int main(){

    long long int m[N][N];
    int p_x=0,p_y=0,h_x=0,h_y=0;
    cin >> p_x >> p_y >> h_x >> h_y;
    //初始化
    for(int i=0;i<N;i++){
        for(int j=0;j<N;j++){
            m[i][j] = 0;
        }
    }
    m[0][0] = 1;
    //计算
    for(int i=0;i<=p_x;i++){
        for(int j=0;j<=p_y;j++){
            if(is_horse(i,j,h_x,h_y)){
                m[i][j] = 0;
                continue;
            }
            if(i==0){
                if(j==0) continue;
                m[i][j] = m[i][j - 1];
                continue;
            }
            if(j==0){
                m[i][j] = m[i-1][j];
                continue;
            }
            m[i][j] = m[i-1][j] + m[i][j-1];
        }
    }
    for(int i=0;i<=p_x;i++){
        for(int j =0;j<=p_y;j++){
            cout << m[i][j] << " ";
        }
        cout << "\n";
    }
    cout << m[p_x][p_y];
    return 0;
}

bool is_horse(int x,int y,int h_x,int h_y){
    int abx = ((x - h_x) < 0) ? (h_x - x) : (x - h_x);
    int aby = ((y - h_y) < 0) ? (h_y - y) : (y - h_y);
    if(abx == 1){
        return (aby==2) ? true : false;
    }else if(abx == 2){
        return (aby==1) ? true : false;
    }
    return (abx == 0) && (aby == 0) ;
}

运行结果如下

解决!

求最小子数组问题

题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/description/
这一题刚开始理解错了,以为随机选n个来组成数组,但实际是子数组(类似字符子串)。因此不用排序就可以解决。最容易想到的就是暴力两个for循环这里就不再放了。简单说下滑动窗口叭。

滑动窗口解法

个人理解,滑动窗口问题最重要的是三步走:

  1. 判断起始位置怎么动(这代表了窗口的滑动)
  2. 判断终止位置怎么动(这也代表了窗口的滑动。ps: 也就是一个管着增大,一个管着减小)
  3. 何时更新滑动窗口大小
    这一题里,终止位置(代码中的j)一直叠加就好了(即在刚开始时,窗口一直增大,直到触发要更新滑动窗口大小的条件)。
    更新滑动窗口大小的条件也很简单,就是sum>=target;
    起始位置要随着滑动窗口大小更新时随着更新,也就是滑动窗口减小(即i向右移动),注意:要同时更新sum。
    代码如下:
#include<iostream>
// #include<algorithm>
#include<vector>
using namespace std;

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int subLen = nums.size()+1;
        int sum = 0;
        int i=0;
        for(int j=0;j<nums.size();j++){
            sum += nums[j];
            while(sum >= target){
                int subl = j - i + 1;
                if(subl < subLen) subLen = subl;
                sum -= nums[i++];
            }
        }

        return (subLen == nums.size()+1) ? 0 : subLen;
    }
};

int main(){

    vector<int> nums = {12,28,83,4,25,26,25,2,25,25,25,12};
    Solution s;
    cout << s.minSubArrayLen(213,nums);
    return 0;
}

更多的东西,代码随想录中动画很好理解,参见:https://www.programmercarl.com/0209.长度最小的子数组.html

链表

删除倒数第N个节点

题目链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/
很容易想到是快慢指针,但是刚开始有几个点没想明白。

思路一

  1. 快慢指针写法:我是使用int i计数,这就会出现慢指针何时等于head的问题,这里应该是n,即让慢指针指向要删除节点的前一个。
  2. 慢指针为空时,代表要删除head

代码如下

#include<iostream>
struct ListNode{
    int val;
    ListNode* next;
    ListNode():val(0),next(nullptr){};
    ListNode(int x):val(x),next(nullptr){};
    ListNode(int x,ListNode* p):val(x),next(p){};
};
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode*p=head;
        ListNode*p_n = nullptr;
        for(int i=0;p!=nullptr;i++){
            p = p->next;
            if(i == n){
                p_n = head;
            }else if(i > n){
                p_n = p_n->next;
            }
        }
        if(p_n == nullptr){
            return head->next;
        }else{
            ListNode* tmp = p_n->next;
            p_n->next = p_n->next->next;
            delete tmp;
            return head;
        }
    }
};

int main(){
    int n = 5;
    ListNode* head,*p;
    ListNode* tmp;
    for(int i=0;i<n;i++){
        tmp = new ListNode(i);
        if(i==0){
            head = tmp;
            p = head;
            continue;
        }
        p->next = tmp;
        p = tmp;
    }
    for(p=head;p!=nullptr;p=p->next){
        std::cout << p->val << "\t";
    }
    Solution s;
    ListNode* r = s.removeNthFromEnd(head,1);
    for(p=head;p!=nullptr;p=p->next){
        std::cout << p->val << "\t";
    }
    return 0;
}

思路二

直接定义快慢指针指向head。遵循如下两步:

  1. 快指针先跑n,若发现中间快指针指向nullptr,则代表要删除头节点或者错误处理(题中保证有结果,则不用判断i是否小于n-1,即不用错误处理)
  2. 后面快指针和慢指针一起跑,直到快指针到链表尾部

代码如下

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* pFast = head;
        ListNode* pSlow = head;
        for (int i=0; i<n; i++){
            if (pFast->next == nullptr) return head->next;
            pFast = pFast->next;
        }

        while(pFast->next){
            pFast = pFast->next;
            pSlow = pSlow->next;
        }

        pSlow->next = pSlow->next->next;
        return head;
    }
};

链表相交

题目链接:https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/description/

思路一

很容易想到暴力解法,就是两层循环遍历。代码如下。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        // 暴力写法
        ListNode* tempA=headA;
        ListNode* tempB=headB;
        while(tempA != nullptr){
            tempB = headB;
            while(tempB != nullptr){
                if(tempB == tempA){
                    return tempA;
                }
                tempB = tempB->next;
            }
            tempA = tempA->next;
        }
        return nullptr;

    }
};

复杂度为O(mn)

思路二

相交的话不可避免要遍历,但是怎么避免两层循环遍历是关键。

朴素想法:要是有类似快慢指针的路子,可以让两个指针分别指向两个链,然后两个指针一起走,时间复杂度就会降下来

问题关键:长度不一,不好指针一起走

解决办法:长的先走长出来的那部分,因为长出的那部分一定不会相交

复杂度这样就将为了O(2n+0.5m),最差复杂度为O(2n+m),最好复杂度为O(2n)(假设n>m

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int len_A = 0,len_B=0;
        ListNode* tempA=headA;
        ListNode* tempB=headB;
        while(tempA!=nullptr){
            tempA = tempA->next;
            len_A++;
        }
        while(tempB!=nullptr){
            tempB = tempB->next;
            len_B++;
        }
        tempA = headA;
        tempB = headB;
        if(len_A > len_B){
            for(int i=len_B;i<len_A;i++){
                tempA = tempA->next;
            }
        }else{
            for(int i=len_A;i<len_B;i++){
                tempB = tempB->next;
            }
        }
        while(tempA != nullptr){
            if(tempA == tempB){
                return tempA;
            }
            tempA=tempA->next;
            tempB=tempB->next;
        }
        return nullptr;
    }
};

链表成环判断

原题出处:https://leetcode.cn/problems/linked-list-cycle-ii/

思路一

一个数组,存储过所有见过的节点指针,当指针重复时,返回

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

这里用到了STL库中的<unordered_set>关联容器
通过hash索引,最好查找情况o(1),最坏情况时o(n)

这种方法实现简单,但是需要较高的空间复杂度(o(n))

思路二

快慢指针,利用的时路程关系,详细推导见题解,再赘述并无太大意思,只需注意理解一点:

  • 在慢指针进入环的第一圈内,快指针一定会和慢指针相遇。因为当慢指针进入环时,快指针的追击距离小于一圈,而快指针速度是慢指针的两倍(即慢指针再走一个追击距离就会被追上,而此距离小于一圈)
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if (head==nullptr){
            return nullptr;
        }
        ListNode* fast = head;
        ListNode* slow = head;
        do{
            fast = fast->next;
            slow = slow->next;
            if(fast != nullptr){
                fast = fast->next;
            }else{
                return nullptr;
            }
        }while(fast!=nullptr && fast!=slow);
        ListNode* ptr = head;
        while(ptr!=slow){
            ptr = ptr->next;
            slow = slow->next;
        }
        return ptr;
    }
};

三数之和

力扣原题:https://leetcode.cn/problems/3sum/

用双指针解法(左右指针),也可以叫三指针
基本思路

  • 排序
  • 一个指针i负责遍历
  • 一个left指针和一个right指针分别指向:i+1数组末尾
  • 若三个指针所指数和小于0,则right--;小于0则left++;等于零则更新right和left,添加结果集;每一轮的结束条件是:left>=right

有几个需要注意的点:

  1. i指针:[-1,-1,-1,0,1,2]这种情况,i指针显然要调,但是比较的方法是 nums[i] == nums[i-1],即要和前一个比较是否相同
  2. 更新left和right不能简单++和--,例如[0,0,0,0,0]这种情况,left和right要一直++和--直到遇到和之前不同的值
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<vector<int>> result_set;
        for(int i=0;i<nums.size() - 2;i++){
            if(nums[i] > 0) break;
            if((i>0) && nums[i] == nums[i-1]) continue;
            int left = i + 1;
            int right = nums.size() - 1;
            while(left != right){
                int tmp_sum = nums[i] + nums[left] + nums[right];
                if (tmp_sum == 0){
                    result_set.push_back(vector<int>{nums[i],nums[left],nums[right]});
                    int temp_left = nums[left];
                    while(nums[left] == temp_left && left!= right) left++;
                    int temp_right = nums[right];
                    while(nums[right] == temp_right && right != left) right--;
                }else if(tmp_sum < 0){
                    left++;
                }else{
                    right--;
                }
            }
        }
        return result_set;
    }
};

四数之和

力扣原题:https://leetcode.cn/problems/4sum/
思路基本一样,只需要两层遍历

注意

  • 对于第二层遍历,其不能越过逻辑界限:i+1(对于i这个界限是0,即正常的数组越界)
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(),nums.end());
        vector<vector<int>> result_vector;
        if(nums.size() < 4){
            return vector<vector<int>> {};
        }
        for(int i=0;i<nums.size()-3;i++){
            if(i>0 && nums[i] == nums[i-1]) continue;
            for(int j=i+1;j<nums.size()-2;j++){
                if(j>i+1 && nums[j] == nums[j-1]) continue;
                int left = j+1;
                int right = nums.size() - 1;
                while(left != right){
                    long long int temp_sum = static_cast<long long int>(nums[i]) + static_cast<long long int>(nums[j]) + static_cast<long long int>(nums[left]) + static_cast<long long int>(nums[right]);
                    if(temp_sum == target){
                        result_vector.push_back(vector<int>{nums[i],nums[j],nums[left],nums[right]});
                        int temp_left = nums[left];
                        while(temp_left == nums[left] && left != right) left++;
                        int temp_right = nums[right];
                        while(temp_right == nums[right] && left != right) right--;
                    }else if(temp_sum > target){
                        right--;
                    }else{
                        left++;
                    }
                }
            }
        }
        return result_vector;
    }
};

本文作者:超超不会写代码

本文链接:https://www.cnblogs.com/cc8910/p/18523573

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   超超不会写代码  阅读(35)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起