本周和大佬同学进行了一起的算法刷题,主要平台就是Leetcode,题型大致分为二叉树和栈。同时我自己也对栈和队列进行了相关的复习。
对于下面的这些题目,有必要写一点刷题总结(详见总结部分)。在此之前我也发了一些关于算法的一些见解,不过这个专栏主要是记录算法周训。
一般来说,刷题可以使用C++,Java,Go。不太推荐使用Python来刷题,因为很多功能也都封装好了。本人习惯于C++刷题,虽然近些时候Java也接触得蛮多,但是对于高校生以C++为主是趋势。前不久,高中NOI联赛很多地区也只能用C++,更不用说考研数据结构部分和复试上机部分也都是指定使用C/C++。当然了,语言并不是问题,关键是思维性,非常重要!
接下来,每周仍会以算法周训为导向,继续加强自己的Code与Debug能力。
以下的题目我也做了几遍,首先是每天的训练,再到今天的博客编写又重新码了一遍。第二次刷的时候思路便清晰了很多,码的速度也变快了不少。所以,掌握题目的最好的办法就是多练多刷几遍!
算法是基本功,掌握算法能力与代码编写能力才会让你更上一层楼!希望共同学习与交流进步!
合并两个有序链表
这个题目本质就是合并链表,需要注意的就是这个链表已经是有序的了。
使用递归注意递归的条件与参数。
ListNode* mergeTwoLists(ListNode * l1,ListNode *l2) {
if(l1 == NULL) return l2;
if(l2 == NULL) return l1;
if(l1->val <= l2->val) {
l1->next = mergeTwoLists(l1->next,l2);
return l1;
}else {
l2->next = mergeTwoLists(l1,l2->next);
return l2;
}
}
当然此题也可以使用循环归并排序,个人感觉也是相当不错的:类似先循环2个都是非空链表,然后进行插入。这里先构造一个头结点为0的head节点,然后通过cur指向并完成插入。这是比较常见的思路。
ListNode* mergeTwoLists(ListNode * l1,ListNode *l2) {
ListNode *head = new ListNode(0);
ListNode *cur = head;
while( l1 && l2) {
if(l1->val < l2->val) {
cur->next = l1;
cur = cur->next;
l1 = l1->next;
}else {
cur->next = l2;
cur = cur->next;
l2 = l2->next;
}
}
if(l1 == NULL) cur->next = l2;
else cur->next = l1;
return head->next;
}
路径总和
这是一个关于二叉树的题目,比较常见,主流思路必定就是递归了,但是该如何递归呢?
首先考虑递归左树,如果没有得到target,那么就得-1然后递归右树。
bool hasPathSum(TreeNode* root, int sum) {
if(root == nullptr) return false;
if(root->left== nullptr && root->right == nullptr) {
return sum == root->val;
}
return hasPathSum(root->left,sum-root->val) || hasPathSum(root->right,sum-root->val);
}
当然也可以使用dfs深搜,并进行相应的剪枝操作
bool res=false ;
void dfs(TreeNode *node,int sum) {
if(!node ) {
return;
}
if(node->left == nullptr && node->right == nullptr) {
if(sum == node->val) res = true;
}
if(!res) dfs(node->left,sum-node->val);
if(!res) dfs(node->right,sum-node->val);
}
bool hasPathSum(TreeNode* root, int sum) {
if(!root) return res;
dfs(root,sum);
return res;
}
对称二叉树
这个题目就比较简单了,常见的从小到大(小问题-大问题)递归的模板。考虑一个子树是否对称然后考虑全体树是否对称。这里需要的是,一个指针从左边开始递归,另一个指针从右边开始递归。
bool fun(TreeNode *root1,TreeNode *root2) {
if( root1==nullptr && root2 == nullptr) return true;
if(root1 == nullptr || root2==nullptr) return false;
return root1->val == root2->val && fun(root1->left,root2->right) && fun(root1->right ,root2->left);
}
bool isSymmetric(TreeNode* root) {
return fun(root,root);
}
或者使用队列来满足条件:
bool isSymmetric(TreeNode* root) {
if(!root) return true;
queue<TreeNode *> que;
que.push(root->left);
que.push(root->right);
while(!que.empty()) {
TreeNode *leftnode = que.front();que.pop();
TreeNode *rightnode = que.front();que.pop();
if(!leftnode && !rightnode) return true;
if(leftnode->val != rightnode->val || !leftnode || !rightnode) return false;
que.push(leftnode->left);
que.push(rightnode->right);
que.push(leftnode->right);
que.push(rightnode->left);
}
return true;
}
相交链表
简单来说,这就是找内存地址一样的节点,注意不是节点中的值。
很明显,遍历便可以解决。
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *p;
ListNode *a = headA,*b = headB;
if( !headB || !headA) return NULL;
while( a != b) {
a = a ? a->next:headB;
b = b ? b->next:headA;
}
return a;
}
当然还有一种暴力的求法,先求出len值,然后进行对齐,最后直接比较。
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *cura=headA,*curb= headB;
int lena = 0,lenb =0;
while(cura) {
lena++;
cura=cura->next;
}
while(curb) {
lenb++;
curb = curb->next;
}
cura = headA;
curb = headB;
if(lenb > lena) {
swap(lena,lenb);
swap(cura,curb);
}
int gap = lena-lenb;
while(gap--) {
cura = cura->next;
}
while(cura) {
if(cura == curb) {
return cura;
}
cura = cura->next;
curb = curb->next;
}
return NULL;
}
两数相加
这个题目有几个需要注意的点,首先是对节点的初始化。需要一个头结点来满足题意。当然关键的还有一个进位标志符。
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *head=nullptr,*tail=nullptr;
int carry=0;
while( l1 || l2 ) {
int x = l1 ? l1->val:0;
int y = l2 ? l2->val:0;
int sum = x+ y+ carry;
if( ! head) {
head = tail = new ListNode(sum%10);
}else {
tail->next = new ListNode(sum%10);
tail = tail->next;
}
carry = sum/10;
if(l1) {
l1 = l1->next;
}
if(l2) {
l2 = l2->next;
}
}
if(carry>0) {
head = new ListNode(carry);
}
return head;
}
当然了这题也同样可以使用递归,毕竟算一位然后可以向上递归到全位。
ListNode* add(ListNode *l1,ListNode *l2,int a) {
if(!l1 && !l2) {return a==0 ? nullptr:new ListNode(a);}
if( l1 != nullptr) {
a += l1->val;
l1 = l1->next;
}
if( l2 != nullptr) {
a += l2->val;
l2 = l2->next;
}
return new ListNode(a%10,add(l1,l2,a/10));
}
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
return add(l1,l2,0);
}
搜索旋转排序数组
很明显的二分查找
int search(vector<int>& nums, int target) {
int i=0,j= nums.size()-1;
int mid;
while ( i <= j) {
mid = (i+j)/2;
if( nums[mid] == target ) return mid;
else if( nums[mid] < nums[j]) {
if(nums[mid] < target &&target <= nums[j]) {
i = mid+1;
}else {
j = mid-1;
}
}
else {
if(nums[i] <= target && target<nums[mid]) {
j = mid-1;
}else {
i = mid +1;
}
}
}
return -1;
}
逆波兰式
ALDS_3_A
典型的栈应用,不过在这里需要对输入字符做一些处理,这里使用的Char型,而不是使用string
//
// Created by 17399 on 2022/8/31.
//
#include <iostream>
#include <stack>
#include <cstdlib>
#include <cstdio>
using namespace std;
int main() {
stack<int> a;
char s[105];
while(scanf("%s",s)!=EOF){
if(s[0] == '+') {
int c = a.top();
a.pop();
int d = a.top();
a.pop();
a.push(c+d);
}else if(s[0] == '*') {
int c = a.top();
a.pop();
int d = a.top();
a.pop();
a.push(c*d);
}else if(s[0] == '/') {
int c = a.top();
a.pop();
int d = a.top();
a.pop();
a.push(c/d);
}else if(s[0] == '-') {;;
int c = a.top();
a.pop();
int d = a.top();
a.pop();
a.push(d-c);
} else{
a.push(atoi(s));
}
}
cout<<a.top()<<endl;
return 0;
}
队列任务完成
使用队列来完成,注意需要使用循环队列来保证范围
//
// Created by 17399 on 2022/9/1.
//
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 10000005
typedef struct pp{
char name[100];
int t;
}p;
int head,tail;
p q[maxn];
void push(p x) {
q[tail] =x;
tail = (tail+1)%maxn;
}
p pop() {
p x = q[head];
head = (head + 1)%maxn;
return x;
}
int main() {
int n,q1 ;
cin>>n>>q1;
int count =0;
for(int i=1;i<=n;i++) {
scanf("%s",q[i].name);
scanf("%d",&q[i].t);
}
p u;
head = 1,tail = n+1;
while( head != tail ) {
u = pop();
int c = u.t;
int z = min(q1,c);
u.t -= z;
count += z;
if (u.t>0) {
push(u);
}else {
cout<<u.name<<" "<<count<<endl;
}
}
return 0;
}
总结
这些题目质量都是相当不错的,不是很难但不是很容易。首先是C++ 的链表构造部分,这里给出的是两种Leetcode的构造部分,这里就没有采用C语言的malloc构造内存了。
ListNode *p = new ListNode(x);
ListNode *p = new ListNode(x,head->next);
在链表部分,主要是明白遍历与下一个节点的重要性。当然,在leetcode部分,需要对节点的NULL进行判断,否则就会出现相关的错误。
比较好的题目就是两数相加,这是一个关于大数相加的改编题,具有相当的典型意义。
在二叉树部分,需要对递归有深刻的理解。特别是深搜,构造等,都是利用此特性来进行编写。
以上题目需要多看,多加练习!