明天程序设计需要打印的 代码总结

1.快慢指针

经典题目--

T1-删除排序数组中的重复项: 

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

代码如下:

class Solution {
public:
    int removeDuplicates(vector& nums) {
        int fast=1;
        int slow=1;
        for(fast=1;fast<=nums.size()-1;fast++)
        {
            if(nums[fast]!=nums[fast-1])//由于是升序排列 所以数组 重复元素是在一起的
            {
                nums[slow++]=nums[fast];
            }
        }
        return slow;
    }
};

2.对撞指针:

T1-两数之和 II - 输入有序数组

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列  ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

代码如下:

class Solution {
public:
    vector twoSum(vector& numbers, int target) {
        //和 上机课的 那个 平方之和的 数 --思路完全一样,都是采用对撞指针
        vector answer;
        int front=0;//理解有误--其实 他说的 是 返回的 时候 返回从1开始的 下标
        int back=numbers.size()-1;
        int sum=0;
        while(front!=back)
        {
            sum=numbers[front]+numbers[back];
            //1.
            if(sum==target)
            {
                return {front+1,back+1};
            }
            //2.
            else
            sum>target?back--:front++;
            
        }
        return {};

    }
};

3.滑动窗口:

T1-长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

代码如下:

class Solution {
public:
    int minSubArrayLen(int target, vector& nums) {
        //滑动窗口的思想--左右边界值--定一动一
        int left=0,right=-1;
        int min_len=nums.size()+1;
        int sum=0;
        while(left=target)
            {
                min_len=min(right-left+1,min_len);
            }
        }
        if(min_len==nums.size()+1)
        return 0;
        else return min_len;
    }
};

4.归并排序:

T1:利用 “归并排序” 对 1个单向链表 进行 排序:

<1> -----关键:

(1)merge_sort函数 : 递归 函数--出口,直到只有1个 或者 0个 元素为止,直接返回这个节点,作用就是 链表 分成 2半, 

(2)merge_sort函数中:因为是链表,所以需要 利用 fast ,slow快慢指针 找到中间位置,然后分别找到 left链表 和 right链表的 头节点(注意把 left链表的 尾节点 设置为 NULL)

(3)merge函数:不需要用递归 实现 ,直接用while循环即可, 直到其中有一个指针为NULL,最后接上剩下的 那个链表(等价于上课 讲到的 2个有序 链表的 合并)

<2>--------代码如下:

/**
 * 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* sortList(ListNode* head) {
        head=merge_sort(head);
        return head;
    }
    ListNode* merge_sort(ListNode* head)
    {
        //出口 ,head长度为1 或 0 直接返回
        if(head == nullptr || head->next==nullptr)
        {
            return head;
        }
 
        //(1)利用 快慢指针 找到中间位置
        ListNode* fast=new ListNode(0);
        ListNode* slow=new ListNode(0);
        fast=head->next; //这种链表没有头节点,每个节点都有val
        slow=head;
        //fast 每次移动2格 , slow移动一格
        while(fast!= NULL && fast->next!=NULL)
        {
            fast=fast->next->next;
            slow=slow->next;
        }
        //(2)此时slow指向的位置就是向下取整的中间位置
        //递归,直到1个元素 或0 个元素
        ListNode* right=new ListNode(0);
        ListNode* left=new ListNode(0);
        right=merge_sort(slow->next); //右边链表的起点
        slow->next=NULL; //左边链表的尾部 置NULL
        left=merge_sort(head);
 
        head=merge(left,right);
        
        //尝试 添加一个新的 堆区空间
        ListNode* ans=new ListNode(0);
        ans=head;
        //show(ans);
        return ans; //head这个指针式传递进来的,可以返回,不是临时变量
    }
    //输出 函数
    void show(ListNode* node)
    {
        while(node!=NULL)
        {
            cout<val<next;
        }
    }
 
    ListNode* merge(ListNode* left,ListNode* right)
    {
 
        //cout<<"left:"<val<<"right:"<val<<"merge一下"<next=NULL;
        while(left!=NULL && right!=NULL)
        {
            if(left->val < right->val)
            {
                //left先进
                root->next= left;
                root=root->next;
                left=left->next;
            }
            else
            {
                //right进
                root->next=right;
                root=root->next;
                right=right->next;
            }
        }
        //(2)处理 剩余的 节点
        if(left!=NULL)
        {
            //左边非空
            root->next=left;
        }
        else if(right != NULL)
        {
            root->next= right;
        }
        return root_->next;
    }
};

T2:对2个有序的数组进行归并:

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

代码如下:

class Solution {
public:
    void merge(vector& nums1, int m, vector& nums2, int n) {
        //利用尾插法--从最后的位置开始 交替防止--不会产生遮掩问题
        int p1=m-1;
        int p2=n-1;
        int cur=m+n-1;
        while(p1>=0&&p2>=0)
        {
            if(nums1[p1]>=nums2[p2])
            {
                nums1[cur--]=nums1[p1--];
            }
            else
            {
                nums1[cur--]=nums2[p2--];
            }
        }
        while(p1>=0)
        {
            nums1[cur--]=nums1[p1--];
        }
        while(p2>=0)
        {
            nums1[cur--]=nums2[p2--];
        }

    }
};

5.数组中的第k个最大的元素--小根堆

T1-数组中的第K个最大元素

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

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

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

代码如下:

法一:直接sort,然后取出倒数第k个元素即可

法二:改进 大根堆排序

class Solution {
public:
//1.小的大根堆 的 构建函数
    void max_heapify(vector &arr,int start,int end)
    {
        //(1)先 初始化 dad节点的 下标,和 son节点的 下标
        int dad=start;
        int son=dad*2+1;
        //(2 然后利用循环进行 小的堆的构建
        while(son<=end)
        {
            //判断:找出 两个子节点中大的哪一个
            if(son+1<=end&&arr[son]=arr[son])
            {
                return ;
            }
            else
            {
                //说明需要进行交换--同时,还要考虑是否--影响 已经构架好的 对结构
                swap(arr[dad],arr[son]);
                dad=son;
                son=dad*2+1;
            }
        }
    }
    //2.改进的 堆排序函数--算了,直接放到里面

    int findKthLargest(vector& nums, int k) {
        //思路:(1)先构建一个大根堆
                //(2)在进行堆排序的 过程中,只需要选出 第k大的 就可以了,改进了
        //(1)
        int len=nums.size();
        for(int i=len/2-1;i>=0;i--)
        {
            max_heapify(nums,i,len-1);
        }
        //(2)
        for(int i=len-1;i>=len-k;i--)
        {
            swap(nums[0],nums[i]);
            max_heapify(nums,0,i-1);
        }
        //nice真不错--漂亮
        return nums[len-k];

    }
};

6.二分查找:

T1-猜数字大小

猜数字游戏的规则如下:

每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。
你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):

-1:我选出的数字比你猜的数字小 pick < num
1:我选出的数字比你猜的数字大 pick > num
0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num
返回我选出的数字。

代码如下:

/** 
 * Forward declaration of guess API.
 * @param  num   your guess
 * @return 	     -1 if num is higher than the picked number
 *			      1 if num is lower than the picked number
 *               otherwise return 0
 * int guess(int num);
 */

class Solution {
public:
    int guessNumber(int n) {
        //利用二分搜索,每次返回中间的数值mid,然后依次二分数值区间
        long long left = 1;
        long long right =n;
        long long mid = (right + left)/2;
        while(right>=left)
        {
            mid = (right + left)/2;
            if(guess(mid) == 0)
            {
                return mid;
            }
            else if(guess(mid) == -1)
            {
                //right 有边界变为 mid-1
                right = mid-1;
            }
            else{
                left = mid+1;
            }
        }
        return mid;
    }
};

7.bfs--广搜:

T1-打开旋转锁

你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转:例如把 '9' 变为 '0','0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。

锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。

列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。

字符串 target 代表可以解锁的数字,你需要给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。

代码如下:

class Solution {
public:
    //一下借鉴他人的答案:--双向 BFS方法
    //1.初始设置:
    string s,t;
    unordered_set st;//作为deadends的禁忌表
    //2.主函数--调用一次bfs函数
    int openLock(vector& deadends, string target) {
    s="0000";
    t=target;
    if(s==t) return 0;
    for(const auto& d: deadends)
    st.insert(d);                      //设置好禁忌表
    if(st.count(s)) return -1;
    int ans=bfs();
    return ans;                       //关键在于调用bfs
    }
    //3.bfs函数
    int bfs()
    {
        //(1)初始化两个队列,用于存放上一次加入队列的 状态
        queue d1,d2;   //d1从s出发,d2从target出发
        //(2)利用哈希表记录已经搜索过的点----避免无限循环!!!--妙
        unordered_map m1,m2;    //示例:"{"1000",1}代表1000这个状态最先由1次变换得到"
        //(3)d1,从s开始,d2从target开始--双向bfs
        d1.push(s);m1[s]=0;
        d2.push(t);m2[t]=0;
        //(4)只要两个队列都不为空,就继续搜索,直到所有的状态都已经被搜索一遍
        while(d1.size()&&d2.size())
        {
            int time=-1;//作为 转换的次数
            if(d1.size()<=d2.size())
            {
                //从较小的队列开始搜索--节约时间
                time=update(d1,m1,m2);
            }else time=update(d2,m2,m1);//前一个是己方遍历过的哈希表, 后一个是 对方遍历过的哈希表
            if(time!=-1) return time;//只要第一次找到直接返回
        }
        return -1;//只要一方空了,说明没有可能了

    }
    //4.定义update函数 --用于向不同方向更新
    int update(queue& q,unordered_map& cur,unordered_map& other)
    {
        //(1)取出队列中的第一个元素,并且从队列中删除
        string t=q.front();q.pop(); //局部变量--遮盖全局变量
        int step=cur[t];//保留这个元素 经过多少次转换得到
        //(2)8种处理
        for(int i=0;i<4;i++)
        {
            for(int j=-1;j<=1;j++)
            {
                //跳过0
                if(j==0) continue;
                //得到变换结果
                int origin=t[i]-'0';
                int next=(origin+j)%10;
                if(next==-1) next=9;//特殊
                string copy=t;//保留原本,处理copy副本
                copy[i]='0'+next;
                //(3)判断 下一步怎么走
                if(st.count(copy)||cur.count(copy)) continue; //已经 搜索过, 或者在禁忌表中
                if(other.count(copy)) return step+1+other[copy];//找到了,返回 汇合步数
                else
                {
                    //都不是--入队--最后一个位置
                    q.push(copy);
                    cur[copy]=step+1;
                }
            }
        }
        //这一轮的8个房间 搜索对象没有找到 返回-1
        return -1;
    }
}; 



//带思考--只要一方空了,就可以 否定其他可能了??why
//答:(1)这种双向 bfs是为了加快 搜索速度;
//     (2)比如说:d1空了,说明从s出发的所有可能达到的状态都已经进入了m1中,而m1仍然没有和m2产生交集
//     (3)注意:m2中可是包好target的啊!!!所以从s出发永远达不到target

8.dfs-递归深度优先搜索

T1-通过添加+ 、- 运算符得到target目标和

给你一个整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

代码如下:

class Solution {
public:
    //定义dfs
    int t=0,size=0;
    int cnt=0;
    void dfs(int sum,vector& nums,int level)
    {
        //(1)层数 +1
        level++;
        //(2)level<=size可以继续递归
        if(level<=size)
        {
            dfs(sum+nums[level-1],nums,level); //加法 深入一层
            dfs(sum-nums[level-1],nums,level); //减法 深入一层
        }
        //(3)出口
        else
        {
            if(sum==t)
            {
                cnt++;
            }
            return;
        }
    }

    int findTargetSumWays(vector& nums, int target) {
    //利用 dfs递归思想--遍历每一种情况
    //1.先设置好 重要的 全局参数
    t=target;
    size=nums.size();
    //2.调用dfs
    dfs(0,nums,0);
    //3.返回结果
   
    return cnt;
    }
};

9.unorder_map哈希表的 常用场景:

T1-两数之和:

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

代码如下:

class Solution {
public:
    vector twoSum(vector& nums, int target) {
    //unordered_map---yyds太妙了!思路:先把nums中的元素 以及对应的下标存进map中,然后遍历查找:
    //1.存储:
    int size=nums.size();
    unordered_map map1;
    for(int i=0;i<=size-1;i++)
    {
        map1[nums[i]]=i;
    }
    //2.遍历查找:
    for(int i=0;i<=size-1;i++)
    {
        if(map1.count(target-nums[i])&&i!=map1[target-nums[i]])
        return {i,map1[target-nums[i]]};
    }
    return {-1,-1};
    }
};

10.旋转二维矩阵:(一个线性代数的问题:转置 + reverse)

给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。

不占用额外内存空间能否做到?

代码如下:

class Solution {
public:
    void rotate(vector>& matrix) {
    //线性代数:有这一道题--先转置,然后行reverse
    int size=matrix.size();
    //1.转置
    for(int i=0;i<=size-1;i++)
    {
        for(int j=i+1;j<=size-1;j++)
        {
            swap(matrix[i][j],matrix[j][i]);
        }
    }
    //2.行reverse
    for(auto &it: matrix)
    {
        reverse(it.begin(),it.end());
    }
    }
};

-3.三次周赛 和 1次双周赛 题目总结:(单独打印......)

-2.包括字符串匹配KMP算法,凸包等 数据结构课程上的 算法题目 - 总结(单独打印......)

-1.动态规划部分的题目--(单独打印......)

你可能感兴趣的:(数据结构,排序算法,链表)