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真好用
优秀的评论:
分享个字母大小写转换的方法:
- 统一转成大写:ch & 0b11011111 简写:ch & 0xDF
- 统一转成小写: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)
链表节点的删除,操作起来很基本,但是要注意:
- 头节点的处理方法:单独处理或者设置一个虚拟头节点(以保证代码的整洁 猜测);尾节点处理(这道题的尾节点似乎没有坑)
最后我用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;
}
};
开学咯,美好的假期结束,更美好
的新一学期开始了🏃♀️🍏😸