习题笔记
- 1. 技巧题
- 2. 常规题
- 3. 算法题
- 1. 分治算法求最大子列和
- 2. x^N的时间复杂度为(logN)的算法
- 3. 递归斐波那契的时间复杂度
- 4. 分离两个数的异或值)数组中数字出现的次数 II
- 5. 出现三次数中找出现一次的
- 6. 数组元素循环右移问题
- 顺序表
- 链表
- 4. PTA 例题
1. 技巧题
1. 消失的数字(按位与)
class Solution {
public:
int missingNumber(vector<int>& nums) {
int n = nums.size();
int ans = 0;
for(int i = 1; i <= n; ++i){
ans ^= i; // [0, n]异或结果
ans ^= nums[i - 1]; // [0, n]异或结果与nums异或结果
}
return ans;
}
};
2.穷举法 7-1 用扑克牌计算24点 (25 分)
注意:
- 固定位置用数组遍历
- 俩俩运算用函数实现
#include<bits/stdc++.h>
using namespace std;
double get(double a,char c,double b)//两个数之间的运算
{
switch(c)
{
case '+':return a+b;break;
case '-':return a-b;break;
case '*':return a*b;break;
case '/':return a/b;break;
}
}
int main()
{
int i,j;
int temp=1;
int a,b,c,d,e,f;
float num[4];
char cha[4]={'+','-','*','/'};
cin>>num[0]>>num[1]>>num[2]>>num[3];
for(i=0;i<=3&&temp;i++)
for(j=0;j<=3&&temp;j++)
for(a=0;a<=3&&temp;a++)
for(b=0;b<=3&&temp;b++)
for(c=0;c<=3&&temp;c++)
for(e=0;e<=3&&temp;e++)
for(f=0;f<=3&&temp;f++)
{
if(i==a||i==c||i==f||a==c||a==f||f==c)
continue;
if(temp&&get(get(get(num[i],cha[j],num[a]),cha[b],num[c]),cha[e],num[f])==24.0)//1.通过两两运算 :(((a先与b)再与c)最后与d)
{
temp=0;
printf("((%.0lf%c%.0lf)%c%.0lf)%c%.0lf\n",num[i],cha[j],num[a],cha[b],num[c],cha[e],num[f]);
// cout<<" i = "<<i<<" a ="<<a<<" c = "<<c<<" f = "<<f<<endl;
}
if(temp&&get(get(num[i],cha[j],num[a]),cha[b],get(num[c],cha[e],num[f]))==24.0)//2.通过两两运算 :((a先与b)再与(c先与d))
{
temp=0;
printf("(%.0lf%c%.0lf)%c(%.0lf%c%.0lf)\n",num[i],cha[j],num[a],cha[b],num[c],cha[e],num[f]);
// cout<<" i = "<<i<<" a ="<<a<<" c = "<<c<<" f = "<<f<<endl;
}
if(temp&&(get(num[i],cha[j],get(num[a],cha[b],num[c])),cha[e],num[f])==24.0)//3.通过两两运算 :((a再与(b先与c))最后于d)
{
temp=0;
printf("(%.0lf%c(%.0lf%c%.0lf))%c%.0lf\n",num[i],cha[j],num[a],cha[b],num[c],cha[e],cha[e]);
// cout<<" i = "<<i<<" a ="<<a<<" c = "<<c<<" f = "<<f<<endl;
}
if(temp&&get(num[i],cha[e],get(get(num[a],cha[b],num[c]),cha[j],num[f]))==24.0)//4.通过两两运算 :(a最后与((b先与c)再与d))
{
temp=0;
printf("%.0lf%c((%.0lf%c%.0lf)%c%.0lf)\n",num[i],cha[e],num[a],cha[b],num[c],cha[j],num[f]);
// cout<<" i = "<<i<<" a ="<<a<<" c = "<<c<<" f = "<<f<<endl;
}
if(temp&&get(num[i],cha[j],get(num[a],cha[b],get(num[c],cha[e],num[f])))==24.0)//5.通过两两运算 :(a最后与(b再与(c先与d)))
{
temp=0;
printf("%.0lf%c(%.0lf%c(%.0lf%c%.0lf))\n",num[i],cha[j],num[a],cha[b],num[c],cha[e],num[f]);
// cout<<" i = "<<i<<" a ="<<a<<" c = "<<c<<" f = "<<f<<endl;
}
}
if(temp==1)
cout<<"-1"<<endl;
}
2. 常规题
1. N个分数求和 (20 分)
递归最大公因数
long long gcd(long long a, long long b)
{
if (b == 0)
return a;
else
return gcd(b, a % b);
}
分数用结构体读入,二维数组也可以
struct fenshu
{
long long fenzi;
long long fenmu;
}a[100];
2. 二分法求多项式单根
函数功能的实现可以用 函数
double f(double x)
{
return a3 * x * x * x + a2 * x * x + a1 * x + a0;
}
阈值——保留2为小数
while (b - a >= 0.01)
3.喝汽水问题
处理空瓶子
while(temp >= 2)
{
sum += temp / 2;
temp = temp / 2 + temp % 2;
}
如果剩余一个空瓶,再借一瓶吗,问面试官
4. 双指针 排奇偶数组
while中while
5. 谁是凶手(逻辑推理)
字符遍历
for (killer = 'a'; killer <= 'd'; killer++)
说真话,意味着表达式的值为1
if ((killer != 'a') + (killer == 'c') + (killer == 'd') + (killer != 'd') == 3)
每个人说对了一半
(p[1] == 2) + (p[0] == 3) == 1
6. 猜名次(逻辑推理)
通过递归,模拟n次的n次循环
for (p[n] = 1; p[n] <= 5; p[n]++)
{
diveRank(p, n + 1); /*通过递归模拟多层循环,
每进一次递归相当于进了一层新的循环。*/
}
判断谁说的对,把所有情况罗列后,带入判断条件中
for (p[0] = 1; p[0] <= 5; p[0]++)
{
for (p[1] = 1; p[1] <= 5; p[1]++)
{
for (p[2] = 1; p[2] <= 5; p[2]++)
{
for (p[3] = 1; p[3] <= 5; p[3]++)
{
for (p[4] = 1; p[4] <= 5; p[4]++) //五层循环遍历
{
//这里是五个人的描述,由于比较表达式只有0和1两个结果,如果要两个条件有且只有一个为真,则可以用比较表达式的值总和为1的方式直接判定。别忘了还要判定不能并列。
if ((p[1] == 2) + (p[0] == 3) == 1 && //B第二,我第三
(p[1] == 2) + (p[4] == 4) == 1 && //我第二,E第四
(p[2] == 1) + (p[3] == 2) == 1 && //我第一,D第二
(p[2] == 5) + (p[3] == 3) == 1 && //C最后,我第三
(p[4] == 4) + (p[0] == 1) == 1 && //我第四,A第一
checkData(p) //不能并列
)
{
for (int i = 0; i < 5; i++)
{
printf("%d ", p[i]);
}
putchar('\n');
}
}
}
}
}
}
7. 公务员面试
按集合 的持续输入
int val;
while(cin >> val)
{
int a[8];
a[0]=val;
for(int i=1;i<7;++i)
先读入一个用于判断循环,然后在大循环里读入集合-1的个数。
8. 二维数组 限制打印
把两个元素假装成一个数组元素
for(int i1 = 0; i1 < j - 2; i1++)
{
for(int j1 = 0; j1 < j -2; j1++)
{
if( ((i1 + j1 <= j-2-1)&&(i1<=j1)) || ((j1+i1 >=j-2-1)&&(i1>=j1)) )
cout << q;
else if((i1 + j1 <= j-2-1)&&(i1>j1))
cout << ' ';
}
9. 阅览室
10. 素数对(6*k ±1)
11. 装箱问题 (20 分)
二重循环
把箱子当成数组,但是箱子的数目得比物品多
12. 调整奇数偶数顺序
left < right 防止数组左边越界
void swap_arr(int arr[], int sz)
{
int left = 0;
int right = sz-1;
int tmp = 0;
while(left<right)
{
// 从前往后,找到一个偶数,找到后停止
while((left<right)&&(arr[left]%2==1))
{
left++;
}
// 从后往前找,找一个奇数,找到后停止
while((left<right)&& (arr[right]%2==0))
{
right--;
}
// 如果偶数和奇数都找到,交换这两个数据的位置
// 然后继续找,直到两个指针相遇
if(left<right)
{
tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
}
}
13. 在递增矩阵中搜索数据
while (j >= 0 && i < y)
{
if (a[i][j] < f) //比我大就向下
{
i++;
}
else if (a[i][j] > f) //比我小就向左
{
j--;
}
else
{
return 1;
}
}
14.输出整数各位数字(/和%)
/ 和 %(10的几位数)就是留下前或后的几位数
15. 黑洞数(数组大小重排)
16. 字符串strstr 判断子串
翻转情况的罗列,就是把原数组原地扩2倍
17. 合并两个数组(把一个数组放在另一个数组)
3. 算法题
1. 分治算法求最大子列和
分治法步骤
- 定义数据
- 递归结束条件
- 分,然后根据分的情况,递归(递归的接受值,也就是递归函数的返回值,也是目的值)
- 治的过程
- 返回目的值
2. x^N的时间复杂度为(logN)的算法
奇偶次幂
奇次幂 用ret 次幂-1
偶次幂 x*x 次幂 /2
3. 递归斐波那契的时间复杂度
4. 分离两个数的异或值)数组中数字出现的次数 II
mask=xor&(-xor) 得到一个数二进制最低的1的值
分两组,一组最低的1为0处理
for(int i=0;i<numsSize;i++){
if((nums[i]&mask)==mask)
ans[0]^=nums[i];
else
ans[1]^=nums[i];
5. 出现三次数中找出现一次的
思路:位运算
数组按位相加(两重循环)
for(int i = 31; i >= 0; i--)
{
for(int j = 0; j < numsSize; j++)
{
bit += nums[j] >> i & 1;
}
ans = ans * 2 + bit % 3;
bit = 0;
}
由位值,求总值
6. 数组元素循环右移问题
直接输出法
本质交换法
ans = ans * 2 + bit % 3;
顺序表
1. 要画图做题,删除有序数组中的重复项
方法一:const修饰两个指针,用于保存数组值,还可以 指坐标
int removeDuplicates(int* nums, int numsSize){
const int *p = (const int *)nums;
const int *q = (const int *)nums;
int size = 0;
if (numsSize > 0) {
size = 1;
}
//p指针为输入数组中不重复值的指针,q指针为变化指针
for (int i = 0; i < numsSize; i++, q++) {
if (*p != *q) {
p = q;
nums[size++] = *q;
}
}
return size;
}
方法二: 双指针,后一个数与前一个数比,重复一定被换
int removeDuplicates(int* nums, int numsSize) {
if (numsSize == 0) {
return 0;
}
int fast = 1, slow = 1;
while (fast < numsSize) {
if (nums[fast] != nums[fast - 1]) {
nums[slow] = nums[fast];
++slow;
}
++fast;
}
return slow;
}
方法三:三指针,
2. 数组整数+一个数
数组的扩容(加法最多 多出一位)
链表
1. 移除链表元素
注意:
- 如果头节点是空 直接用while循环判断就好了
- 如果头节点就是需要删除的
- 删除的时候,需要保留上一个点的指针,所以需要再创建一个指针
2. 206. 反转链表
迭代(双指针+一个指针用于指向下一个)
递归
递归就是先找到最后一个
接下来的操作,就是对每个最后一个的操作
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;
ListNode *tail = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return tail;
}
};
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* prev = NULL;
struct ListNode* curr = head;
while (curr) {
struct ListNode* next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
3. 876. 链表的中间结点
快慢结点 循环判断条件的设定(假设法)
假设有3个节点
假设有2个节点
此两种情况下,指针是否循环,便是判断条件
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
4. 链表中倒数第k个结点
注意
- k的值<=0 或者大于结点数
普通解法(遍历两次)
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if (!pListHead || k <= 0) return nullptr;
int n = 0;
ListNode *cur = pListHead;
while (cur) {
cur = cur->next;
++n;
}
if (n < k) return nullptr;
n -= k;
while (n--) {
pListHead = pListHead->next;
}
return pListHead;
}
};
快慢指针
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if (!pListHead || k <= 0) return nullptr;
auto slow = pListHead, fast = pListHead;
while (k--) {
if (fast)
fast = fast->next;
else
return nullptr; //如果单链表长度 < K,直接返回
}
while (fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
};
5. 21. 合并两个有序链表
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* preHead = new ListNode(-1);
ListNode* prev = preHead;
while (l1 != nullptr && l2 != nullptr) {
if (l1->val < l2->val) {
prev->next = l1;
l1 = l1->next;
} else {
prev->next = l2;
l2 = l2->next;
}
prev = prev->next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev->next = l1 == nullptr ? l2 : l1;
return preHead->next;
}
};
6. 链表分割
注意:
- 这个链表不是结构体指针,就是结构体
- 把小于X的链接一个链表,另一个是另一个链表,然后再连接
public class Partition {
public ListNode partition(ListNode pHead, int x) {
ListNode cur = pHead;
ListNode bs = null;
ListNode be = null;
ListNode as = null;
ListNode ae = null;
while(cur != null){
if(cur.val < x){
if(bs == null){
bs = cur;
be = cur;
}else{
be.next = cur;
be = be.next;
}
}else{
if(as == null){
as = cur;
ae = cur;
}else{
ae.next = cur;
ae = ae.next;
}
}
cur = cur.next;
}
if(bs == null){
return as;
}
be.next = as;
if(as != null){
ae.next = null;
}
return bs;
}
}
7. 判断链表是否为回文结构
方法一:把链表放置到数组中
方法二:用快慢指针取链表中间位置,然后再从中间位置开始遍历判断
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
bool chkPalindrome(ListNode* A)
{
struct ListNode*slow=A;
struct ListNode*fast=A;
while(fast->next)
{
slow=slow->next;
fast=fast->next;
}
struct ListNode*change=NULL;
struct ListNode*head=slow;
while(head)
{
struct ListNode*next=slow->next;
head->next=change;
change=head;//重新置头
head=next;
}
while(slow)
{
if(A->val!=change->val)
{
return false;
}
slow=slow->next;
change=change->next;
}
return true;
}
};
8. 相交链表(双指针)
注意:
- 首先判断指针是否为空
- 当一个链表遍历完成后,让它为另一个指针的头,继续判断等待另一个链表遍历完成后,成为另一个头,此时两个链表相差的节点数就被抹平了。然后再次遍历,如果还没有相等的,那么就会返回两个链表的末尾的NULL
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if (headA == NULL || headB == NULL) {
return NULL;
}
struct ListNode *pA = headA, *pB = headB;
while (pA != pB) {
pA = pA == NULL ? headB : pA->next;
pB = pB == NULL ? headA : pB->next;
}
return pA;
}
先走相差的个数
9. 判断循环链表
bool hasCycle(struct ListNode* head) {
if (head == NULL || head->next == NULL) {
return false;
}
struct ListNode* slow = head;
struct ListNode* fast = head->next;
while (slow != fast) {
if (fast == NULL || fast->next == NULL) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
10. 返回循环链表中的,入环位置
入环位置,和快慢指针的相遇位置是有关系的
struct ListNode* detectCycle(struct ListNode* head) {
struct ListNode *slow = head, *fast = head;
while (fast != NULL) {
slow = slow->next;
if (fast->next == NULL) {
return NULL;
}
fast = fast->next->next;
if (fast == slow) {
struct ListNode* ptr = head;
while (ptr != slow) {
ptr = ptr->next;
slow = slow->next;
}
return ptr;
}
}
return NULL;
}
11. 复制带随机指针的链表
注意:
- 因为复制节点,random所指向的节点,也应该是复制而来的,所以如果在第一个就直接去找random,是不行的,起码要全部复制出来后,再去处理random。
- 所以复制节点链表,需要技巧,就是先复制到每个原节点的后面,然后就可以用random- >next->next找到复制节点
12. 对链表进行插入排序
注意:
- 遍历的时候,注意需要保留什么指针,增加什么指针
- 结构体也是指针
13. 删除链表中重复的结点
注意:
- 删除节点,在题中就直接把指针跳过这个结点就好了,而不是真的free掉
- 极端情况的讨论,便是都是成对出现的,都要删除,那么辅助指针的个数就会更加明确
14. 删除 链表中给定结点地址
直接改变自身的地址,那么上个指针的也对相应就变了
15. 两个有序链表序列的交集
两个有序链表序列的交集
typedef 对指针
typedef struct node {
int data;
struct node* next;
}*LinkList;
链表的读取,需要一个指针记录头,一个记录读取,一个记录下一个
LinkList createList() { //尾插法建立链表
LinkList H = (LinkList)malloc(sizeof(struct node));
LinkList p = H;
int ch;
scanf("%d", &ch);
while (ch != -1) {
LinkList q = (LinkList)malloc(sizeof(struct node));
q->data = ch;
p->next = q;
p = q;
scanf("%d", &ch);
}
p->next = NULL;
return H->next;
链表结构体的新开辟
1.用函数 listnode* buylistnode(int x)
2.结构体构造函数
4. PTA 例题
1. 两个有序序列的中位数 (25 分)
动态数组的开辟(new,malloc)
int *s1 = new int[n];
int *s2 = new int[n];
样例:
int* a = (int*)malloc(sizeof(int) * n);
int* a = new int[n];
双指针算法(用于 两个数组比较)
for( i = 0, j = 0; m != 0;)
{
if(s1[i]<=s2[j])
{
i++,m--;
}
else
{
j++,m--;
}
}