算法题收获汇总

目录

001 PAT (Basic Level) 1002 写出这个数(20 分)

收获:

1、对于求一个整数的各个位置上数字的两种思路:

  1. 用%/来求,将结果保存在数组中;
  2. 也可以直接将其转换成字符串,字符串相当于一个只读char数组,又因为char和int可以隐式转换,所以用字符串数组中的元素减去‘0’即为其元素值

2、对于与整数存在一一对应的关系,可以将其用数组存储起来利用下标进行访问;也可以采用流程控制语句进行判断。

002 PAT (Basic Level)1005 继续(3n+1)猜想 (25 分)

收获:

**1、如果题目中的条件中有 正整数 n (1<n≤100)的值这样的字眼,即既规定了正整数,又限定了其范围,这种情况下可以考虑用空间换时间按的方法,用辅助数组。

2、最后一个输出的数字不加空格:**
先输出空格,再输出元素,第一个元素之前不加空格,对 i 进行判断即可。**

3、用双数组可以实现字典,字典中的key存放在arr1中,其对应的value存放在以key为下标的arr2中,遍历arr1,在arr2中下标属于arr1的值中查找符合条件的值。形如arr2[ arr1[ i ] ]

003 PAT (Basic Level)1007 素数对猜想 (20 分)

收获:

1、直接用运算符运算要比调用库函数效率高。例如本题中判断素数用j * j <= i的效率要高于j <= Math.Pow(i,0.5),要注意这个细节,正是这个细节,有一个测试就超时了。
2、能不申请新变量尽量不要申请,降低空间复杂度。

004 PAT (Basic Level) 1008 数组元素循环右移问题 (20 分)

收获:

因为逆置之前并没有判断右移位数和数组长度的关系,导致部分测试没有通过,所以要注意这个细节:在逆置之前应该首先判断右移的位数,如果右移位数是数组长度的整数倍,则直接将数组元素输出即可。

005 逆置字符串

1、可以用split函数将其分割成字符串数组,然后通过交换实现元素逆置
2、可以借助栈的先进后出的特性,先压栈再出栈

006 1010 一元多项式求导 (25 分)

1、关于特殊情况的判断,可以在一般情况判断前or后进行判断。

007 1013 数素数 (20 分)

判断素数

static bool IsPrime(int n)
        {
            for (int i = 2; i * i <= n; i++)
            {
                if (n % i == 0)
                {
                    return false;
                }
            }
            return true;
        }

008 PAT (Basic Level)1015 德才论 (25 分)

1、掌握两个用于集合排序的接口的使用情况;
2、注意比较逻辑的写法:

每类考生按总分降序排列,当某类考生中有多人总分相同时,按其德分降序排列;若德分也并列,则按准考证号的升序输出。

先写总分不同的情况下的处理逻辑,然后写德分不同的情况下的处理逻辑,最后写准考证号不同的处理逻辑。

		if (x.De + x.Cai != y.De + y.Cai)
                return (y.De + y.Cai) - (x.De + x.Cai);
            else if (x.De != y.De)
                return y.De - x.De;
            else
                return x.Num - y.Num;

009 1016 部分A+B (15 分)

1、注意将char类型当作对应的整型数值进行加减乘除时,要减去’0’
2、处理整数的各位元素时,可以将其变成字符串,遍历每一位时相当于char类型,更加方便快捷。

010 重点 1017 A除以B (20 分)

1、思路很重要,因为所给的数值过大,不可能直接除或者模,所以要想到除法的计算过程;
2、边界情况一定要注意。

011 重点1018 锤子剪刀布 (20 分)

实现这个题并不难,但是写的代码有些冗余,造成时间复杂度和空间复杂度比较高,一定要搞清楚题目的核心需求,减少变量的申请,用最少的资源来实现功能可以进行如下优化:
1、降低空间复杂度,因为甲乙的输赢是对立的,所以只用设立两个变量,jiaWin和yiWin,就可以推出甲乙各自的输赢平手次数,又因为我们只需要得到甲乙获胜的次数和获胜的手势,所以我们就只需要考虑6种可能的情况(算上平手总共有九种可能的情况);
2、用数组来模拟字典:如果让我们求出现频率最高的元素,设置两个变量,一个记录最大值,一个记录最大值下标;然后可以将所有元素放入数组的对应位置,通过之前求出的最大值下标来找出目标元素。

011 重点1019 数字黑洞 (20 分)

1、一定要考虑每一种可能出现的情况;
2、想办法将不同的情况整合到一起,避免代码冗余。例如本题中的do-while设计将输入是6174和其他数值整合,这样就不用另外对6174进行判断;while中的判断条件是s != “6174”&& s!=“0000”,将0000和其他结果整合,不用对结果0000做另外判断。

012 双指针同时移动

获取两个字符串中最大相同子串。比如:
str1 = "abcwerthelloyuiodef“;str2 = “cvhellobnm”
提示:将短的那个串进行长度依次递减的子串与较长的串比较。

长度一定,双指针同时移动可以找出所有符合情况的结果

 如果存在多个长度相同的最大相同子串。 此时先返回String[],后面可以用集合中的ArrayList替换,较方便
    public String[] findSame1(String str1, String str2) {
        if (str1 == null || str2 == null)
            return null;
        if (str1.length() == 0 || str2.length() == 0)
            return new String[]{};
        String minStr = str1.length() >= str2.length() ? str2 : str1;
        String maxStr = str1.length() < str2.length() ? str2 : str1;
        StringBuilder s = new StringBuilder();
        // 每一轮减少一个字母,内层循环判断所有的组合是否符合题意
        for (int i = 0; i < minStr.length(); i++) {
            for (int start = 0, end = minStr.length() - i; end <= minStr.length(); start++, end++) {
                String str = minStr.substring(start, end);
                if (maxStr.contains(str)) {
                    s.append(str + ",");
                }
            }
       // 如果存在多个长度相同的最大相同子串,内层for循环可以全部找出来,找出来之后就不用再进行下一轮外层循环了
            if (s.length() > 0)
                break;
        }
        String[] split = s.delete(s.length() - 1, s.length()).toString().split(",");
        return split;
    }

1.双指针专题

1.1解题思路

  1. 确定两个指针的作用;
  2. 确定两个指针的移动方向:两侧向内,一前一后向后;

1.2 快慢指针

1.2.1 核心思想

如果数组中慢指针slow指向当前结果序列的下一个元素,此时比较的是slow-1指向的元素和fast指向的元素,满足条件会将fast指向的元素赋值给slow指向的位置;如果数组中慢指针slow指向当前结果序列的末尾元素,此时比较的是slow指向的元素和fast指向的元素,满足条件会将fast指向的元素赋值给slow+1指向的位置。(一般来说数组中的慢指针指向的都是当前结果序列的下一个元素,因为初始值一般设为0;链表中指向的是当前结果序列的末尾元素)
快指针fast指向的是当前正在判断的元素;
无论fast指向的元素是否满足要求,fast指针每次都会向后移动一位;而slow指针只有在满足条件时向后移动。

1.2.2 相关题目

80. 删除有序数组中的重复项 II
26. 删除有序数组中的重复项
82. 删除排序链表中的重复元素 II
83. 删除排序链表中的重复元素

1.2.3 删除有序数组重复项通解(此解法采用的是slow指向当前结果序列的下一个元素)

注:如果是无序数组,可以先进行排序。
参考文章

k表示保留 k 个相同数字
int process(int[] nums, int k) {
    int u = 0; 
    for (int x : nums) {
    // 利用了短路或的特性,将前k个元素先复制到数组中,然后再进行比较去重;
    // 也可以直接将快慢指针的初始值设为k。
        if (u < k || nums[u - k] != x) nums[u++] = x;
    }
    return u;
}

1.3 滑动窗口

1.3.1 使用场景

子串问题

1.3.2 核心思想 参考文章

初始时,两个指针都位于索引为0的位置,快指针向右移动,一直到符合题意时停止,然后左指针向右移动,不符合题意时停止,然后重复以上两个步骤,直到右指针到达边界。
右指针移动是为了寻找一个「可行解」,左指针移动是为了优化这个「可行解」,最终找到最优解。

1.3.3 解题步骤

需要考虑的4个问题:

  1. 移动快指针扩大窗口时,需要更新哪些数据?(移动快慢指针更新数据是对称的)
  2. 找到「可行解」后,窗口应该暂停扩大,开始移动慢指针缩小窗口?(此处需要维护一个变量res来充当窗口缩小的条件)
  3. 移动慢指针缩小窗口时,需要更新哪些数据?(移动快慢指针更新数据是对称的)
  4. 应该在扩大窗口时还是缩小窗口时更新结果值?(只需要考虑扩大窗口和缩小窗口两种情况)

1.3.4 标识找到「可行解」的变量 res

使用场景:题目中有两个字符串,如果题目中只有一个字符串,不需要使用变量res。
扩大窗口时,如果滑动窗口中的元素记录的次数和needs中一样,就让res++;
缩小窗口时,如果滑动窗口中的元素记录的次数和needs中一样,就让res–,然后将滑动窗口中的元素记录的次数-1。

1.3.5 模板

/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;
    
    int left = 0, right = 0;
    int valid = 0; 
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 增大窗口
        right++;
        // 进行滑动窗口内数据的一系列更新
        ...

        /*** debug 输出的位置 ***/
        printf("window: [%d, %d)\n", left, right);
        /********************/
        
        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            char d = s[left];
            // 缩小窗口
            left++;
            // 进行滑动窗口内数据的一系列更新
            ...
        }
    }
}

2.链表

2.1 技巧

  1. 如果head会被移除,那么一定要设置一个虚拟节点指向head,题目如果要求返回的是head,那么只要返回虚拟节点的next节点即可;
  2. 链表中可以使用一个变量来充当两个指针,cur是慢指针,cur.next是快指针。写代码时不要关注cur和cur.next的关系,只需将cur.next当作右指针,通过比较cur.next和cur.next.next来确定cur的值。82. 删除排序链表中的重复元素 II
  3. 如果要删除所有的重复元素,重复数字可以使用一个变量保存。82. 删除排序链表中的重复元素 II
  4. 链表中的慢指针指向的结果序列的末尾元素,故初始位置应该是一个空节点或者是虚拟节点; 反转链表中使用的是空节点,因为链表反转后末尾元素指向的是null。

2.1.1 相关题目

19.删除链表的倒数第 N 个结点
82. 删除排序链表中的重复元素 II

3.二分查找

如果是有序数组且要求时间复杂度是O(log(n)),可以使用二分查找。

  1. 如果元素至多出现1次,例如寻找唯一元素,搜索插入点,推荐使用模板3;
  2. 如果存在重复元素找边界或者是寻找峰值,查找左边界采用模板1,查找右边界采用模板2。
  3. 模板1和模板2需要先确定下一轮搜索区间,再去确定计算mid时需不需要加1.

3.1 整数二分算法模板

3.1.1 模板1

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
public int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + ((r - l) >> 1); // 此处注意>>的优先级低于+
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}

使用场景: 第一个满足条件的mid一种情况可能是左端点,另一种情况一定是在左端点右边。所以满足条件时,将mid赋值给r,继续向左寻找,直至找到左端点为止。
注意: 返回值可以是left,也可以是right,因为最后退出循环时left一定等于right;

3.1.2 模板2

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l +((r - l + 1) >> 1);
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

使用场景: 第一个满足条件的mid一种情况可能是右端点,另一种情况一定是在右端点左边。所以满足条件时,将mid赋值给l,下一轮搜索区间是[mid,right],继续向右寻找,直至找到右端点为止。
注意:

  1. 此处mid = l +((r - l + 1) >> 1),因为当r = l + 1时,如果mid = l + ((r - l) >> 1),此时mid = l,就会陷入死循环中。
  2. 返回值可以是left,也可以是right,因为最后退出循环时left一定等于right;

3.1.3 模板3

    public int search(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while(left <= right){
            mid = left + (right - left) /2;
            if(nums[mid] == target){
                return mid;
            }else if(nums[mid] > target){
                right = mid -1;
            }else{
                left = mid + 1;
            }
        }
        return left;//如果在数组中没有找到target,则返回target在数组中对应的插入位置left,left位置的元素大于target,而left-1(right)位置的元素小于target。
    }

使用场景: 目标元素如果在数组中至多出现一次,那么使用此模板;如果数组中存在目标元素,则返回其对应的下标;如果不存在,则返回目标元素在数组中的插入位置。
注意:如果在数组中没有找到target,则返回target在数组中对应的插入位置left,left位置的元素大于target,而left-1(或者是right)位置的元素小于target。

3.1.4 模板1和模板2使用步骤

34. 在排序数组中查找元素的第一个和最后一个位置为例:

  1. 寻找左边界
    先考虑nums[mid]=target时,接下来的搜索区域是哪里,因为寻找的是左边界,所以应该去mid的左边寻找,所以就可以和target<nums[mid]合并,又因为nums[mid]可能是左边界,所以令right = mid,然后else中一定是left = mid + 1因为left不可能等于right或者right+1,所以mid不可能等于right,所以mid向下取整。
    如果数组中不一定存在目标值,循环结束后需要判断nums[right]是否等于target。
    public int searchFirstTarget(int[] nums, int target) {
        int left = 0, right = nums.length - 1, mid;
        while (left < right) {
            mid = left + (right - left ) / 2;
            if (nums[mid] >= target) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        if (nums[left] == target)
            return left;
        return -1;
    }
  1. 寻找右边界
    右边界的思路与左边界同理。因为right可能等于left+1,所以mid可能等于left,为了防止死循环,mid向上取整。
    public int searchLastTarget(int[] nums, int target) {
        int left = 0, right = nums.length - 1, mid;
        while (left < right) {
            mid = left + (right - left + 1) / 2;
            if (nums[mid] <= target) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值