太久没做题了,总感觉看见题干就开始晕了。
先从简单的题做起
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
,判断字符串是否有效。有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
很经典的用栈判断左右括号是否合法,和大一比起来的写法的区别在于(看了题解):用哈希表来查询右括号匹配的左括号
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]
的金钱。给你整数
n
、k
、budget
,下标从 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 != j
、i != 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;
}
};