2023-2024寒假刷题日记

本文分享了解决一系列编程题目的过程,涉及数组去重、链表操作(如两数相加、合并、删除倒数第N个节点),字符串处理(如Z字形变换、有效括号),以及数学逻辑游戏(Nim游戏)的解决方案,展示了使用双指针、哈希表等技术的实例。
摘要由CSDN通过智能技术生成

太久没做题了,总感觉看见题干就开始晕了。

先从简单的题做起

1/24

26. 删除有序数组中的重复项(数组 双指针)

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

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k 。

很简单的双指针,一快一慢,慢的指针用来控制数组变化,快的决定如何变化 

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int n = nums.size();
        if(n == 0)
        {
            return 0;
        }
        int f = 1, s = 1;
        while(f<n)
        {
            if(nums[f]!=nums[f-1])
            {
                nums[s]=nums[f];
                s++;
            }
            f++;
        }
        return s;
    }
};

做了两天的每日一题,2765、2865两个

感觉没啥好说的,只会双循环想不到更优解。。。

做一个链表题来回忆一下链表

2.两数相加(链表)

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

创建新链表并设头结点值为-1:

ListNode* head = new ListNode(-1); 

给链表的节点赋值:

l3->next=new ListNode(sum%10);

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        //诸位相加,不要化成整数再加
        ListNode* head = new ListNode(-1);
        ListNode* l3= head;
        int sum = 0;//和
        int ifp = 0;//进位标志
        while(l1!=nullptr||l2!=nullptr)
        {
            sum = 0;
            if(l1!=nullptr)
            {
                sum += l1->val;
                l1=l1->next;
            }
            if(l2!=nullptr)
            {
                sum +=l2->val;
                l2=l2->next;
            }
            if(ifp ==1)sum++;
            l3->next=new ListNode(sum%10);
            l3=l3->next;
            if(sum>=10)ifp=1;
            else ifp=0;
        }
        if(ifp)
        {
            l3->next = new ListNode(1);
        }
        return head->next;
    }
};

再做一个链表题巩固一下,题目本身很简单,就是合并,比较有看头的是递归算法:

21.合并两个有序链表(链表)

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

我们判断 l1 和 l2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。(调用递归) 

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

1/25

新的一天新的每日一题,今天的2859还挺简单的

然后继续做链表题,新学到一个方法就是用哑节点(哑结点->next=head)用于处理特殊头部;

19.删除链表的倒数第N个节点(链表)

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

用了笨办法,先遍历获得总节点数,然后将需要删除的节点前的节点都改为其父节点的值。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* h = head;
        int sumn=0;
        int nn=n;
        while(h->next!=nullptr)
        {
             sumn++;
             h=h->next;
        }
        n=sumn-n;
        int num=head->val;
        h=head->next;
        while(n>=0)
        {
            int num2=h->val;
            h->val=num;
            num=num2;
            h=h->next;
            n--;
        }

        return head->next;

1/26

今天的每日一题居然是困难,我人都看傻了,直接放弃hhh

复习一下字符串8,之前学的时候就头疼这个

substr函数
substr(a,b)截取字符串的第a到第b位

神奇的循环方法:

for(char c : s)//遍历字符串中的每个字符c

6.z字形变换(字符串)

将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:

P   A   H   N
A P L S I I G
Y   I   R

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);

看了题解,用一个flag作为掉头标志,将原字符串填入numRows个子字符串中(每个代表一行)

class Solution {
public:
    string convert(string s, int numRows) {
        if(numRows<2)
        return s;
        vector<string> rows(numRows);
        int i=0,flag=-1;//i控制当前字符插入到哪个row里
        for(char c : s)//遍历字符串中的每个字符c
        {
            rows[i].push_back(c);//把c插入到第i个row的尾部
            if(i==0||i==numRows-1)
            {
                flag=-flag;//掉头
            }
            i+=flag;
        }
        string ans;
       for (const string &row : rows)
            ans += row;
        return ans;


    }
};

20.有效的括号(字符串)

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

很经典的用栈判断左右括号是否合法,和大一比起来的写法的区别在于(看了题解):用哈希表来查询右括号匹配的左括号

class Solution {
public:
    bool isValid(string s) {
        
            int n = s.size();
            if(n%2==1){
                return false;//奇数个括号直接false
            }

            unordered_map<char,char> getleft={
                //键为右括号,值为左括号
                {')', '('},
                {']', '['},
                {'}', '{'}
            };

            stack<char> stk;//栈
            for(char c:s){
                if(getleft.count(c))//如果是右括号
                {
                    if(stk.empty()||stk.top()!=getleft[c])
                    {
                        return false;
                    }
                    stk.pop();
                }
                else{
                    stk.push(c);
                }
            }      
        return stk.empty();
    }
};

1/27

今天的每日一题是:

2861.最大合金数(二分)

假设你是一家合金制造公司的老板,你的公司使用多种金属来制造合金。现在共有 n 种不同类型的金属可以使用,并且你可以使用 k 台机器来制造合金。每台机器都需要特定数量的每种金属来创建合金。

对于第 i 台机器而言,创建合金需要 composition[i][j] 份 j 类型金属。最初,你拥有 stock[i] 份 i 类型金属,而每购入一份 i 类型金属需要花费 cost[i] 的金钱。

给你整数 nkbudget,下标从 1 开始的二维数组 composition,两个下标从 1 开始的数组 stock 和 cost,请你在预算不超过 budget 金钱的前提下,最大化 公司制造合金的数量。

所有合金都需要由同一台机器制造。

返回公司可以制造的最大合金数。

(看了题解)使用二分查找,但是我也不知道怎么能想到直接用二分,第一反应是挨个算每个机器能制造的最大合金数(第一个机器从0开始循环,后面的机器从已有的最大数开始循环。。。)

class Solution {
public:
    int maxNumberOfAlloys(int n, int k, int budget, vector<vector<int>>& composition, vector<int>& stock, vector<int>& cost) {
        //不直接计算答案,而是通过判断特定数量的合金能否制造来查找答案
        int left = 1, right = 2e8, ans = 0;//下界设为1,上界设为2^8
        while(left<=right)
        {
            int mid=(left+right)/2;
            bool valid = false;
            for(int i = 0;i<k;i++)
            {
                long long spend = 0;
                for(int j=0;j<n;j++)
                {
                    spend+=max(static_cast<long long>(composition[i][j])*mid-stock[j],0LL)*cost[j];
                    //所需元素数量(根据当前的合金生产水平)与可用库存之间的差异。如果可用库存足够,它返回 0;否则,它返回亏空的数量。
                }
                if(spend<=budget){
                    valid= true;
                    break;
                }
            }
            if(valid)
            {
                ans=mid;
                left=mid+1;
            }
            else{
                right=mid-1;

            }
        }
 return ans;
    }
};

17.电话号码的字母组合(dfs)

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

本来是想复习一下哈希表,但是根本没用哈希表

也是看的题解()

用dfs深度优先,通过回溯获得根节点到最末子节点的路径来获得所有排列方式。

class Solution {
public:
    string tmp;
    vector<string> board={"", "", "abc", "def", "ghi","jkl","mno","pqrs","tuv","wxyz"};//用于查找
    vector<string> ans;

    void dfs(int pos,string digits)//深度优先,相当于计算根节点到各个子节点共多少条路
    //pos代表层数,digits代表按键字符串
    {
        if(pos==digits.size()){//到最末的子节点了,将这种方法放入答案
            ans.push_back(tmp);
            return;
        }
        int num=digits[pos]-'0';//按到哪个键
        for(int i=0;i<board[num].size();i++)
        {
            tmp.push_back(board[num][i]);//这个键代表的字母放入序列
            dfs(pos+1,digits);//搜下一层
            tmp.pop_back();//回溯
        }

    }

    vector<string> letterCombinations(string digits) {
        if(digits.size()==0)
        return{};
        dfs(0,digits);
        return ans;


    }
};

新学到的auto:提示编译器根据变量的值来推导变量的类型,也就是说不用自己定义数据类型,常用与for循环

49.字母异位词分组(字符串 哈希)

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

我是一个没有感情的读题解代码机器呵呵。。。

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        //创建映射
        unordered_map<string,vector<string>> mp;
        //遍历输入的字符串向量 strs 中的每个字符串
        for(string& str:strs){
            string key = str;
            sort(key.begin(),key.end());
            //将字符串 str 添加到名为 mp 的映射(map)中指定 key 对应的值(value)的末尾。
            //如果没有这个key,将自动创建
            mp[key].emplace_back(str);
        }
        vector<vector<string>> ans;
        for(auto it=mp.begin();it!=mp.end();it++)
        {
            //将映射中每个键对应的字符串向量添加到 ans 中。
            ans.emplace_back(it->second);
        }
        return ans;

    }
};

1/29

昨天一道题都没做,罪过罪过

今天的每日一题又是苦难,直接知难而退

数组双指针题:

15.三数之和(双指针)

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

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

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

双指针,用一个i循环,配合双指针,这三个加起来=0 

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n=nums.size();
        sort(nums.begin(),nums.end());
        vector<vector<int>> ans;
        if(n<3)return ans;
        //i作为最小的一个,i+左指针+右指针=0
        for(int i=0;i<n;i++){
            if(i>0){//确保不重复
                if(nums[i]==nums[i-1]){
                    continue;
                }
            }
            if(nums[i]>0)break;
            int left=i+1,right=n-1;
            while(left<right){
                if(nums[i]+nums[left]+nums[right]<0){
                    left++;
                }else if(nums[i]+nums[left]+nums[right]>0){
                    right--;
                }else{
                    ans.push_back({nums[i],nums[left],nums[right]});
                    left++;right--;
                    while(left<right&&nums[left]==nums[left-1])left++;//跳过重复
                    while(left<right&&nums[right]==nums[right+1])right--;
                }
            }
        }
        return ans;
    }
};

128.最长连续序列(哈希)

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

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

先把所有数放到哈希表里方便查找;然后循环对每个数进行 判断:如果没有找到(这个数-1)就将其作为一个序列的开头,然后不断查找这个序列的下一个数来计算这个序列的长度

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        int ans=0;
        unordered_set<int> num_set(nums.begin(),nums.end());
        int lens;
        for(int num:num_set){
            if(!num_set.count(num-1)){//如果它是序列的开头
            lens=1;
            while(num_set.count(num+1)){//能找到该序列的下一个数
                lens++;
                num++;
            }
            ans=max(ans,lens);
            }
        }
        return ans;

    }
};

1.两数之和(哈希)

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

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

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

用了哈希表查找 

看了题解发现不需要单独赋值,只用一个for循环即可(一边查找一边赋值也不影响,还省时间)

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> ans;
        int n=nums.size();
        unordered_map<int,int> hash;
        for(int i=0;i<n;i++){
            hash[nums[i]]=i;
        }
        for(int i=0;i<n;i++){
            //确保不是两个当前数相加得出的
            if(hash.find(target-nums[i])!=0&&i!=hash[target-nums[i]])
            {
                ans.push_back(i);
                ans.push_back(hash[target-nums[i]]);
                break;
            }
        }
        return ans;
    }
};

2/1

开始三天打鱼两天晒网了。。

160.相交链表(链表)

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

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

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

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

题解的巧解: 

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* h1=headA;
        ListNode* h2=headB;
        //重点就是两个指针遍历完一遍两个链表所经过的节点数相同
        //所以两个指针同步next
        //相遇的时候就是首个公共节点
        while(h1!=h2)
        {
            if(h1!=NULL)
            h1=h1->next;
            else
            h1=headB;
            if(h2!=NULL)
            h2=h2->next;
            else
            h2=headA;

        }
        return h1;
    }
};

2/4

292.Nim游戏

你和你的朋友,两个人一起玩 Nim 游戏

  • 桌子上有一堆石头。
  • 你们轮流进行自己的回合, 你作为先手 
  • 每一回合,轮到的人拿掉 1 - 3 块石头。
  • 拿掉最后一块石头的人就是获胜者。

假设你们每一步都是最优解。请编写一个函数,来判断你是否可以在给定石头数量为 n 的情况下赢得游戏。如果可以赢,返回 true;否则,返回 false 。

今天的每日一题就是找规律 ,刚看完题感觉是动态规划来着

class Solution {
public:
    bool canWinNim(int n) {
        //谁面对还剩四颗 谁输 
        //谁面对5、6、7 谁赢
        //谁面对8谁输
        if(n%4==0)
        return false;
        else
        return true;

    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值