算法-算法笔记

输入问题

如果一直空格输出 没告诉你输出多少个 则

int n;
    cin>>n;
    cin.ignore();//这里输入一般是1 abc dd如果没有这个 
    //n接收了1 然后后面的s1会接收 abc dd就会多个空格 cin.ignore();就是忽略这个空格

 string s1,tmp;
        getline(cin,s1);//这里不遇到回车不结束
        //所以一定要字符串分割
/**字符分割/
	string  b, c;
	stringstream d(s1);//直接通过空格分割
	d >> b;
	d >> c;
int num;
while(cin>>num)
data.push_back(num);

碰到相同字母组成 对字母进行排序 然后用哈希表存储
碰到求和或者数组序列 用滑动窗口双指针

数组求和(带去重)

void cersion(vector<int>&nums,vector<int>now,int target,int index)
{
    if(target == 0)
    {
        res.push_back(now);
        return;
    }
    if(index == nums.size())
    {
        return;
    }
    int last = INT_MIN;
    for(int i = index ;i<nums.size();i++)
    {
        if(nums[i]<=target)
        {
            if(last == nums[i])
            continue;
            last = nums[i];
            now.push_back(nums[i]);
            target-=nums[i];
            cersion(nums,now,target,i+1);
            target += nums[i];
            now.pop_back();
        }
    }
}
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> now;
        sort(candidates.begin(),candidates.end());
        cersion(candidates,now,target,0);
        return res;
    }

剑指 Offer 03. 数组中重复的数字

做法

数组中找重复数字我们第一个能想到的就是一一遍历 然后拿hash表记录下来
只要有一个键的值超过了1 那就直接输出
我的办法也是 可开辟一个vectorcan每个数字值就是他的位置 然后出现一次的就把值标位false 下次再遇见can[i] == false这个值的时候就直接输出

更好的办法

核心思想
一个重要的点是题目中数一定都比总数量n小 所以如果没有重复的数字 在0-n这些坑里面应该放着0-n数字对齐 也就是0号坑里面一定是0 所以我们就在每次遍历的时候把这些数字放回去 假设num[i] = x 那说明num[i]这个数字应该属于x这个坑 我们就不把他和num[x](也就是num[num[i]])做交换 那在交换的如果这个数字是第二次出现的 在他交换的时候肯定已经放着相同的数字了
从头开始遍历每一个数
例如
0 1 2 3 4 坑
2 2 4 1 2 萝卜
第一次找2 将2和4交换 放回2坑
0 1 2 3 4 坑
4 2 2 1 2 萝卜
接下去就换下一个数 2 将他放到2坑的时候 发现那里已经是2了 就说明他重复了

实现代码

 bool swap(int &a,int &b)
    {
        if(a == a)
            return true;
        int temp = a;
        a= b;
        b = temp;
        return false;
    }
    int findRepeatNumber(vector<int>& nums) {
        for(int i =0;i<nums.size();i++)
        {
            if(nums[i] != i)
            {
                if(swap(nums[i],nums[nums[i]]))
                    return nums[i];
            }
        }
        return -1;
    }

剑指 Offer 05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = “We are happy.”
输出:“We%20are%20happy.”
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

做法

第一时间想的就是遍历一遍 用另外一个string存字符 碰到’ '就依次push_back(‘%’) push_back(‘2’) push_back(‘0’)

更好的办法

貌似还没有

剑指 Offer 09. 用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

示例 1:

输入:
[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:

输入:
[“CQueue”,“deleteHead”,“appendTail”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方法

一个栈用来添加数据 一个栈用来删除数据 如果删除栈空了 就把添加栈的而所有数据放过来
核心思想 目的就是要能删到栈底的元素
因为栈是删除栈顶元素 把一个栈的元素放到另一个栈里 原来栈顶元素就会变成栈底元素了 原栈底元素就在栈顶 这样就跟队一样了

代码

class CQueue {
    stack<int> del,add;
public:
    CQueue() {
        while(!del.empty())
        {
            del.pop();
        }
        while(!add.empty())
        {
            add.pop();
        }

    }
    
    void appendTail(int value) {//插入元素
    add.push(value);
    }
    
    int deleteHead() {//删除元素
    int out;
    if(del.empty())
    {
        if(add.empty())
        {
            return -1;
        }
        while(!add.empty())
        {
            del.push(add.top());
            add.pop();
        }
        out = del.top();
        del.pop();
    }
    else
    {
        out = del.top();
        del.pop();
    }
    return out;
    }
};
/*
栈的特点是先进后出
队的特点是先进先出 队尾插入 队头删除
是两个栈反向链接吗?

队本来是怎么实现的
链表:

剑指 Offer 10- I. 斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:1
示例 2:

输入:n = 5
输出:5

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

做法

正常就是从i =0 和1的时候是0和1
很明显的递归终止条件 直接递归 return f(n-1)+f(n-2);
这个时候会发现时间是超时的 因为有很多重复的运算
所以就需要用个东西记一下过去的东西就行了 实际上只需要记两项 然后加起来就行了

代码

/*迭代*/
    int fib(int n) {
        int mod = 1e9+7;
        if(n == 0||n == 1)
        {
            return n;
        }
          int first=0;
        int second=1;
        int third=0;
        for(int i=2;i<=n;i++){
            third=(first+second)%mod;
            first=second;
            second=third;
        }
        return third%mod;
    }

剑指 Offer 15. 二进制中1的个数

n = n & (n - 1);
//例如 1011
1011 1010
10101010 1001
10001000 0111
0000

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

示例:

输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

双指针
一个是偶数的位置 一个是奇数的位置
void swap(int &a,int &b){
        int temp = a;
        a = b;
        b = temp;
    }
    vector<int> exchange(vector<int>& nums)
    {
        int dou = nums.size()-1;
        for(int i = 0;i<nums.size();i++)
        {
            if(dou == i)
                return nums;
            if(nums[i]%2 == 0)
            {
                swap(nums[i],nums[dou]);
                dou--;
                i--;
            }
        }
        return nums;
    }

快慢指针 快指针一直走找奇数位置 快指针会经过所有奇数的位置  快指针不会管慢指针指的是哪  
    void swap(int &a,int &b){
        int temp = a;
        a = b;
        b = temp;
    }
    vector<int> exchange(vector<int>& nums)
    {
        int fast = 0;int low = 0;
        while(fast<nums.size())
        {
        if(nums[fast]%2 == 1)
        {
            swap(nums[fast],nums[low]);
            low++;
        }
        fast++;
        }
        return nums;
    }

剑指 Offer 22. 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

做法

快慢指针
先让快指针走k步 k走到链表尾部的时候 慢指针就走到了倒数第k个位置了

ListNode* getKthFromEnd(ListNode* head, int k) 
    {
    ListNode* fast = head;
    ListNode *low = head;
    while(k)
    {
        k--;
        fast = fast->next;
    }
    while(fast)
    {
        fast = fast->next;
        low = low->next;
    }
    return low;
    }

如果碰到二叉树 要用z字型输出

要么是到奇数行用 先用一个vector存储 然后再从尾部输出出来 就是实际上就是存两次
还有一种就是用双向队列 从头和从尾插入

等差数列求和公式

a1×n+(n×(n-1))/2×d

关于不同字母 数字的

一个是要用unordered_map除了可以记下是否有 一个是记下他上次出现的位置 方便直接左边界直接放到他的右边一格

关于回文数

第一个是怎么判断回文数
就是判断从l = 0 和r = size-1;
也可以用栈的先进后出性质 进行对比
不断地靠拢判断
那么第二个点就是用动态规划来算 因为回文数去掉头尾还是回文数 所以我们可以把他优化成子问题 但是他有一个要注意点的点是 他如果是用dp[i][j]
状态转移方程是dp[l][r] = dp[l+1][r-1]&&(nums[l] == nums[r])
l是左边 r是右边 但是这个时候因为r一定要l大 所以就是在右上三角进行 那么如果按正常的l是行r是列 就会发现你从上到下一行一行更新的 l+1了 此时你还没有下一行的数据 所以我们可以一列一列更新 也就是竖着更新就可以解决这个问题
解决这个方法还可以从后面往前面遍历 也就是l为

for(int l = n-1;l>=0;l--)
{
	for(int r = l+1;r<n;r++)
	{
		
	}
}

大概率就是bfs dfs

如果在牛客碰到树
大概率就是struct tree{
int val ;int left;int right;
}tree[];
基本不可能建树

二叉树重建

中序遍历如果知道了根节点 他的左边是左子树的节点 右边是右子树的节点 也就是可以通过中序遍历知道左右子树的节点数量
前序遍历就能知道 第一个一定是根节点 如果有左节点 那就后面跟的一定是左根节点
后序遍历就一样了

关于数字求和 什么两数之和 三数之和

一般就是要么就哈希表存数字 方便寻找 要么是双指针进行查找

最大公约数 最小公倍数

任意n和n-1都是互质 也就是n和n-1的公因数只有1 而最大公倍数一定是n*(n-1)

如何球最大公因式

辗转相除法

我们求72和86
那么我们假设有一个数x可以整除72和86
那么可以把86分为 72 + 14
因为x可以整除72 所以 14也能被整除
那么我们就变成求14和72最大公因式
同上 72 = 14 + 58
58和14
58 = 14 + 44
14和44
44 = 30 + 14
30和14
30 = 14 + 16
14和16
16 = 14+ 2
14和2
14 = 2+12
。。。。
最后就是0和2

int gcd(int a, int b) {
 int t;
 while(b!=0) {
  t=a%b;//就是能减就一直减
  a=b;
  b=t;
 }
 return a;
}

求最小公倍数

假设知道a和b的最大公因数x 那么最小公倍数是 a*b/x

实现乘法

long mul(long a, long k) {
	long ans = 0;
	while (k > 0) {
		if ((k & 1) == 1) ans += a;
		k >>= 1;
		a += a;
	}
	return ans;
}

实现除法

实现除二

a>>1;
或者
(left + right+1)>>1就是对left和right 除2
+1是为了避免死循环 如果最后left = 0 right = 1 就会进去死循环

相同前缀字母

就可以用字典树

多组字符串组合进行全排列时

data是需要被排列的
请添加图片描述
核心思想比如 第一个数组 man 和woman 一定是在重新组成的字符串的第一个位置 也就是pos == n-1的地方 然后for循环就是让每一个第一个数组里面的元素都组成一个递归 都呆一次第一的位置 bfs吧

void bfs(vector<vector<string>> &data,int pos,vector<string> &ans,string tmp){
    if(pos == -1){
        int t = tmp.size();
        tmp = tmp.substr(0,t-1);
        ans.push_back(tmp);
        return;
    }
    for(int i=0;i<data[pos].size();i++){
        bfs(data,pos-1,ans,data[pos][i]+"-"+tmp);
    }
}

旋转数组问题

最核心的就是拿中间的值和最右边那个值做对比 可以知道哪一边是有序的

求解一个数组中数字的各种组合的和

例如[1,2,3,4]
就是求
1 2
1 3
1 4
2 3
2 4
3 4
1 2 3
1 2 4
2 3 4
1 2 3 4

		//sum是数列中所有数字的和 scores[i]存储的是数组中每一个数字
        int[] dp = new int[20001];//保持这个值是否可以被求和到
        // 求解背包问题
        dp[0] = 1;//0是可以得到的
        dp[sum] = 1;//所有数字的和自然可以得到
        for(int i = 0; i < n; i++){//遍历数组中每一个数字
            dp[scores[i]] = 1;//只有这一个数字 可以得到这个值[scores[i]]
            for(int j = 0; j <= sum; j++){//从0开始试 直到最大的值sum
                if(dp[j] == 1 && j - scores[i] >= 0)
                    dp[j - scores[i]] = 1;
                    //dp[j] == 1表示这个数是可以得到的 例如sum-scores[i-1]也就是上一个循环中的值 然后再让这个值-现在这个scores[i]肯定也是存在的 意思就是除了scores[i]和scores[i-1]其他数组中的值都加了
            }
        }

最后dp中所有的是1的就是可以得到的值了
例如[1,2,3] sum = 6
dp数组最初为
请添加图片描述
第一次执行就是dp[scores[i]] = 1; 和循环里面的 j == sum的时候dp[sum] == 1
所以执行dp[j - scores[i]] = 1; 也就是dp[sum - scores[i]] = 1 也就是dp[6-1] = 1
请添加图片描述
后面就是
请添加图片描述

另一种方法循环递归

void dfs(vector<int> nums,vector<int>now,int index)
{
	if();
	for()
}

链表

可以用a[1] = 2;这样表示链表 也就是1号的下一个是2号

判断有没有环

快慢指针:快指针走两步 慢指针走一步 如果他们倆相遇了 就说明有环
哈希存起来
并查集 如果两个人祖先是相同的 则说明是一个环 因为请添加图片描述
你只有可能在接34的时候 才会发现祖宗相同

拓扑排序 如果有元素不在拓扑排序中就说明有环

丑数问题

他的核心就是这些丑数都是由他给出的那些质数互乘组成
例如这些数 [1,2,4,7,8,13,14,16,19,26,28,32]
就是由[2,7,13,19]和1组成 例如4就是2*2
那么我们想要知道这些数 方法很多
第一种就是从2开始(因为0不行 而1本来就是)不断地尝试+1 让这个数字不断的/数组中的这些质数 如果能除尽那就说明吗是丑数 但是这个时间太长了
第二种开始我们就换个思路 既然这些都是数组中质数组成 那么我们就让这些数不断的互乘 我们在第二种中是用小顶堆 然后先将1放进小顶堆 然后取出小顶堆堆顶的数字 乘数组中所有的质数 然后再放回小顶堆 还要用个unordered_map来去重 这种方法时间也很久 但是不超时 主要是整理小顶堆要时间
第三种就是n指针每一个指针都代表质数数组中的一个数 而生成的丑数中每个人都可以和质数数组中的每个数组乘一次 然后我们要找出最小的那个放进丑数数组中
请添加图片描述
具体看别人的题解

检查是否是字母或者数字的函数

char a = 'c';
 if(isalnum(a));

将大写变成小写

如果不是大写字母就不会有变化 返回原来的值 例如’1’就返回’1’

char a = 'A';
a = tolower(a);

两个相同数字问题

相同数字之间异或操作是0 也就是4^4 = 0
而零异或任何数字都是那个数字 0^8 = 8
我们就就可以知道 2^2^3^4^9^3^4 = 9因为其他相同的就会被抵消掉

二进制中 数字转字符串 字符串转数字

自己实现的

/*将数字6转换成字符串011*/
string i_s(int nums)
{
    string res = "";
    while (nums)
    {
        res = res + (char)('0' + nums % 2);
        nums = nums >> 1;
    }
    return res;
}
/*二进制的字符串例如"011"(6) 转换成数字6 也就是二进制011*/
int s_i(string nums)
{
    int res = 0;
    for (int i = nums.size() - 1; i >= 0; i--)
    {
        //if (i != 0)
        res = res << 1;
        if (nums[i] == '1')
            res = res + 1;
    }
    return res;
}

内置方法

atoi()字符串转数字

这个是直接转成数字 例如

string a = "123";
int b = atoi(a.c_str());

请添加图片描述
而上面转化就是转化成二进制代表的数字 0,2,7,3,6

to_string()数字转字符串

也就是直接转 6转成"6"

求共同祖先

方法

快慢指针 滑动窗口(也就是双指针 左右指针) 动态规划 bfs dfs 字典树 排序 unordered_map

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值