力扣 2021 1-2月

66.加一(0129)

hello again,手生了,今天开始捡起来吧。

这题…权当又碰到vector的insert吧,其他没什么,大数加法的基本操作,嗯。

class Solution {
public:
    vector<int> plusOne(vector<int>& digits) {
        int iter = digits.size() - 1;
        int carry = 1, mid;
        for(int i = iter; i>=0; i--)
        {
            mid = digits[i] + carry;
            digits[i] = mid % 10;
            carry = mid / 10;
        }
        if(carry) digits.insert(begin(digits), carry);//(迭代器,数字x)在迭代器指向元素前插入x
        return digits;
    }
};

88. 合并两个有序数组(0130)

要求原地merge,关键点是从后往前比较,从后往前进行插入。

并且注意这里num1的大小就是m+n的,所以这里可以有三个指针:

  • current:从m+n开始,往前走,表示当前指针;
  • m:指向num1当前处理的元素
  • n:指向num2当前处理的元素

好优美的别人的代码:

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p = m-- + n-- - 1;
        while (m >= 0 && n >= 0) {
            nums1[p--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];
        }
        
        while (n >= 0) {
            nums1[p--] = nums2[n--];
        }
    }

我的

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int curr = m + n - 1;
        m--;
        n--;
        while(m >= 0 && n >= 0)
        {
            nums1[curr--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];
        }
        // 剩下无非就两种情况了,nums1或nums2没完,分别对应m>=0和n>=0
        while(m >= 0)
        {// 因为是原地合并,所以这啥都不用做,因此这一段以试可以删去(容易忘加break)
            break;
        }
        while(n >= 0)
        {
            nums1[curr--] = nums2[n--];
        }
    }
};

手继续变热中…

101. 对称二叉树(0131)

镜像,只不过是在递归的时候左右分支的变化游戏而已了,挺好

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        //如果是空的话直接返回真,否则开始递归
        return root == NULL ? true : isSymmetric_(root->left, root->right);
    }

    bool isSymmetric_(TreeNode* l, TreeNode* r)
    {
        //如果左右子树都为空,则返回真
        if(l == NULL && r == NULL)
            return true;
        //如果只有其中一个为空,则返回假(这句还非得放在上一句下面)
        if(l == NULL || r == NULL)
            return false;
        //如果都不为空,值若不相等,则返回假
        if(l->val != r->val)
            return false;
        
        return isSymmetric_(l->left, r->right) && isSymmetric_(l->right, r->left);//注意左右区分
        //第一次wa原因是!l 不等于 l == NULL
    }
};

104. 二叉树的最大深度(0131)

今天bp线上the show演唱会诶~~~~…

自己写的 感觉不是很聪明的亚子

class Solution {
public:
    int maxDepth(TreeNode* root) {
        return countDepth(root);
    }

    int countDepth(TreeNode* node)
    {
        if(node != NULL) //如果该节点非空,则返回1+子树的深度
            return 1 + max(countDepth(node->left), countDepth(node->right));
        else // 该节点为空,则返回0
            return 0;
    }
};

别人的

class Solution {
    public int maxDepth(TreeNode root) {
        return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }
}

🐶自己会笨一点

108. 将有序数组转换为二叉搜索树(0201)

第一遍我以为我wa了,打印出来发现mid不加1和标准答案不一样,但讲道理答案也不应该唯一

class Solution {
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        //目的是返回一棵树的根节点
        return BST(nums, 0, nums.size() - 1);
    }

    TreeNode* BST(vector<int>& nums, int left, int right)
    {
        //终止条件
        if(left > right)
            return NULL;gh
            
        //中间节点的生成
        int mid = (left + right) / 2;
        TreeNode* root = new TreeNode(nums[mid]);
        //开始递归了
        root->left = BST(nums, left, mid - 1);
        root->right = BST(nums, mid + 1, right);

        //最终返回根节点root
        return root;
    }
};

左闭右闭会方便一点;评论区有人说不用(left + right) / 2而用left + (right - left) / 2可以防止溢出,我记下了;终止条件也很典型

121. 买卖股票的最佳时机(0202)

第一遍两个for循环直接超时(还是个小学生,嗯)

本菜鸟什么时候才能掌握DP呢,这算是十分基础的动态规划了,甚至都没有打表

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        // [] 的情况
        if(prices.empty()) return 0;

        //DP 当前i的最大收益=max(i-1的最大收益, 当前i的价格-当前所记录下来的最小的minPrice)
        int minPrice = prices[0], maxProf = 0;
        for(int i = 0; i < prices.size(); i++)
        {//分别更新minPrice和maxProf
            minPrice = min(minPrice, prices[i]);
            maxProf = max(maxProf, prices[i] - minPrice);
        }
        return maxProf;
    }
};

为啥这么慢?

执行用时:176 ms, 在所有 C++ 提交中击败了5.20%的用户

内存消耗:91 MB, 在所有 C++ 提交中击败了23.11%的用户

评论区有人说leecode增加了测试样例…甚至有人暴力都过了😅 并且这个时间和其他原因是不是也有什么关系,同样代码第二次去跑,还快了…

执行用时:112 ms, 在所有 C++ 提交中击败了26.79%的用户

内存消耗:90.9 MB, 在所有 C++ 提交中击败了25.81%的用户

122. 买卖股票的最佳时机 II(0203)

(脑筋急转弯)不是dp,只要比前一天的高就能卖出去,当天卖了当天就可以买进,所以是降低了难度

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int prof = 0, previous_p = prices[0];
        for(int i = 1; i < prices.size(); i++)
        {
            if(prices[i] > previous_p)
            {//只要比前一天大的就能卖出了
                prof += prices[i] - previous_p;
            }
            previous_p = prices[i];
        }
        return prof;
    }
};

125. 验证回文串(0203)

wq老师的经典回文,双‘指针’就可以搞定,我还不会高阶的操作方法:

class Solution {
public:
    bool isPalindrome(string s) {
        if(s.empty())
            return true;
        int h = 0, t = s.length() - 1;
        while(h < t)
        {
            if(!(isalnum(s[h])))
            {
                h++;
                continue;
            }
            if(!(isalnum(s[t])))
            {
                t--;
                continue;
            }

            if(tolower(s[h]) != tolower(s[t]))
                return false;
            else
            {
                h++;
                t--;
            }
        }
        return true;
    }
};

唉c++的这些islnum,tolower真好用

优秀的评论:

分享个字母大小写转换的方法:

  1. 统一转成大写:ch & 0b11011111 简写:ch & 0xDF
  2. 统一转成小写:ch | 0b00100000 简写:ch | 0x20

比较的时候注意加上小括号哦,因为位运算优先级比较低。

使用示例:

if((s.charAt(i ++) & 0xDF) != (s.charAt(j --) & 0xDF)) return false; 

136. 只出现一次的数字(0204)

一个异或,还是wq老师的初代题目

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int xor_ = 0;
        for(int i = 0; i < nums.size(); i++)
        {
            xor_ ^= nums[i];
        }
        return xor_;
    }
};

160. 相交链表(0219)

妙啊,是自己想不出来的程度。这样的定式下,无论怎样两个链表的指针都会在结点相遇的;而不会相遇的两个指针也都会最总跑到对方的null指针中,然后再返回去。

美丽美丽

/**
 * 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) {
        //哇 好妙的解法啊,利用总距离相等
        if(headA == NULL || headB == NULL) return NULL;
        ListNode *a = headA, *b = headB;
        while(a != b)
        {
            a = (a == NULL ? headB : a->next);
            b = (b == NULL ? headA : b->next);
        }
        return b;
    }
};

167. 两数之和 II - 输入有序数组

双指针对于这种,看得见排序和长度的十分有效,这是另外一种变化形式。并且他很好的利用了有序,直接进行从两头向中间靠拢的一遍for循环

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        // 有序的,用双指针
        int left = 0, right = numbers.size() - 1;
        while(left != right)
        {
            if(numbers[left] + numbers[right] > target)
            {
                right--;
            }
            else if(numbers[left] + numbers[right] < target)
            {
                left++;
            }
            else if(numbers[left] + numbers[right] == target)
            {
                return {left + 1, right + 1};
            }
        }
        return {0, 0};
    }
};

我的代码有些冗余,看着不够简洁。另外注释太少了

169. 多数元素(0221)

用老式的再开一个数组分别记录次数,过于浪费时间空间了,今天学习了摩尔投票算法

简单来说就是,不同的数就消去,相同的就留着;和敌方打仗,个体战斗水平相当的情况下,我方的数量超过总参战人数的一半(对应题目的超过N/2),我方就会胜利

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int maj = 0, count = 0;
        for(int i = 0; i < nums.size(); i++)
        {
            if(count == 0)
            {// 如果此时count是0(无论是开始的初始值为0还是被减为0了),则就令当前指向的元素为maj
                maj = nums[i];
                count++;
            }
            else maj == nums[i] ? count++ : count--;
        }
        return maj;
    }
};

还有另外一种解法:快排+直接取n/2位置上的元素,(求第k大的数)

172. 阶乘后的零(0222)

是数学题,一看就会,一做就废。可以循环和递归来做

class Solution {
public:
    int trailingZeroes(int n) {
        //1. 5*2有一个0;10*n也有一个0,而10的因子中有一个5
        //2. 除以5,数个数,即可
        int count = 0;
        while(n / 5 != 0)
        {
            count += n / 5;
            n /= 5;
        }
        return count;
    }
};

这是一个惊人的简洁美丽的递归解法:

public int trailingZeroes(int n) {
        return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5);
    }

功力很深厚啊

190. 颠倒二进制位(0223)

Integer.reverse(n)源码如下:

n = (n & 0x55555555) << 1 | (n >>> 1) & 0x55555555;
n = (n & 0x33333333) << 2 | (n >>> 2) & 0x33333333;
n = (n & 0x0f0f0f0f) << 4 | (n >>> 4) & 0x0f0f0f0f;
n = (n << 24) | ((n & 0xff00) << 8) |
            ((n >>> 8) & 0xff00) | (n >>> 24);
return n;

🐂🍺

class Solution {
public:
    uint32_t reverseBits(uint32_t n) {
        uint32_t res = 0;
        int i = 32;
        while(i--)
        {
            res <<= 1;//res左移,留出新的空子给后面的
            res += n & 1;//按位和1相与可以提炼出n的最后一位数字
            n >>= 1;//n右移1位,这一位已经用过了
        }
        return res;

        //整个过程,res从高位到底位逐渐生长,n从低位到高位给他复制过去。while循环和与运算相配合
        //还有很多类似的while循环+双指针配合的题
    }
};

191. 位1的个数(0224)

可以算是位运算的技巧了,n&(n-1)会消掉靠后的那个1,然后不断迭代,什么时候n迭代成0了就结束count了

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int count = 0;
        while(n != 0)
        {
            n &= (n - 1);
            count++;
        }
        return count;
    }
};

198. 打家劫舍(0225)

动态规划,不需要打表,只需要两个变量来记录dp[i-1]dp[i-2]即可,空间复杂度降到了O(n)

关键的转移方程

res = max(a + nums[i], b);//(打劫这家, 不打劫这家)

对于dp[i]来说,关键点就一个:对当前这户,到底打不打。二元1/0问题,就对应着max()里面的两个选项

class Solution {
public:
    int rob(vector<int>& nums) {
        int res = 0, a = 0, b = 0; // a是dp[i-2], b是dp[i-1]
        for(int i = 0; i < nums.size(); i++)
        {
            res = max(a + nums[i], b);//(打劫这家, 不打劫这家)
            //更新a和b
            a = b;
            b = res;
        }
        return res;
    }
};

203. 移除链表元素(0226)

链表节点的删除,操作起来很基本,但是要注意:

  1. 头节点的处理方法:单独处理或者设置一个虚拟头节点(以保证代码的整洁 猜测);尾节点处理(这道题的尾节点似乎没有坑)

最后我用dummy的时候返回的仍然是head,这就不行,因为当这组[1] 1测试点出现的时候,我输出的还是[1]而不是[]因此还是要注意

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //用一个虚拟头节点,否则的话就需要单独考虑头节点val相等和为空的情况
        ListNode* dummy = new ListNode(-1);
        dummy->next = head;
        ListNode* p = dummy;
        while(p->next != NULL)
        {
            if(p->next->val == val)
            {
                p->next = p->next->next;
            }
            else
            {
                p = p->next;
            }
        }
        // return head; // 有问题,当输入为[1] 1的时候 这个返回的还是[1]而不是[]
        return dummy->next;
    }
};

dummy head~ dummy dummy head~~~

206. 反转链表(0227)

今天返校了,很好此题不难,我可以做完后就休息了嘻嘻

常规题,分别用两个量表示之前和之后的节点。(这么简单我还是没有做的很爽快…)

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* curr = head;
        ListNode* pre = NULL;
        ListNode* nex = NULL;
        while(curr != NULL)
        {
            nex = curr->next;
            curr->next = pre;

            pre = curr;
            curr = nex;
        }
        return pre;
    }
};

219. 存在重复元素 II(0228)

unordered_map就是没有排序的map,底层是哈希,用法和map一致STL标准库中无序关联容器中的一种(4之1)

这就是用值去map中找,找到了对下标进行比较,若成功则返回true,不成功则更新此value最新出现的位置;反之如果没有找到,就把这个<value, 下标>这样的键值对放入map中

class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        unordered_map<int, int> m;
        for(int i = 0; i < nums.size(); i++)
        {
            auto iter = m.find(nums[i]);
            if(iter != m.end())
            {// 在哈希m中找到了和nums[i]相等的键
                if(abs(i - m[nums[i]]) <= k)// 如果满足题目要求
                    return true;
                // 否则就更新最新的值所在的下标的位置
                m[nums[i]] = i;
            }
            else
            {//没有找到值相等的 的话,就放入map中<value值, 下标>
                m[nums[i]] = i;
            }
        }
        //运行到这里还没有true,那就是false了
        return false;
    }
};

开学咯,美好的假期结束,更美好的新一学期开始了🏃‍♀️🍏😸

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值