冲算法笔试题!【算法】

  • 计算机语言和开发平台日新月异,但万变不离其宗的是那些算法和理论。
  • 算法和理论永远是程序员最重要的内功,秃头也得学呀

记录部分 当时的思路或者学习其他优秀作者的思路,也帮自己重新梳理逻辑,争取加深印象,加油 (^-^)V

末尾0的个数

在这里插入图片描述
在这里插入图片描述

思路:计算1~n的因数 , 一共有多少个5。

举例100: n = 100/5 + 100/5/5 = 24

( 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 )
其中 25 50 75 100 都有2个因数为5

n/5 是计算因数包含5的数的个数,n /5 /5 是计算因数为25的个数,同样 n/5/5/5是计算因数为125的个数。

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int res = 0;
        while(n/5>0){
            res+=n/5;
            n/=5;
        }
        System.out.println(res);
    }
}

寻找丑数

滴滴中等笔试真题:
在这里插入图片描述
为了防止重复计算,较好的做法是将之前计算过的丑数记录下来,这样判断n 是否为丑数时,就只需要判断n%2==0 || n%3==0 || n%5==0 ,如果成立则继续判断已经计算出来的 n/2 || n/3 || n/5 是否为丑数,就防止了重复计算;

这里用ArrayList做存储已经计算出的丑数。

import java.util.*;
public class Main{
    public static void main(String args[]){
        int n,cur=5;
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        if(n<=5){
            System.out.println(n);
            return;
        }
        List<Integer> list = new ArrayList();
        list.add(0);
        list.add(1);//1
        list.add(1);//2
        list.add(1);//3
        list.add(1);//4
        list.add(1);//5
        while(n>5){
            cur++;
            if(cur%2==0 && list.get(cur/2)==1
                    || cur%3==0 && list.get(cur/3)==1
                    ||cur%5==0 && list.get(cur/5)==1)
            {
                list.add(1);
                n--;
            }
            else{
                list.add(0);
            }
        }
        System.out.println(cur);
    }
}

螺旋矩阵

在这里插入图片描述
在这里插入图片描述
没有特殊知识点,只要代码不写错,注意细节就好。

class Solution {
   public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> res = new ArrayList<>();
        if(matrix == null) return res;
        spiral(res,matrix,0,matrix[0].length-1,0,matrix.length-1);
        return res;
    }
    public void spiral(List<Integer> res,int[][] matrix,int l1,int l2,int r1,int r2){
        if(l1>l2 || r1>r2) return;
        for (int i = l1; i <= l2 ; i++) {
            res.add(matrix[r1][i]);
        }
		
        if(l1==l2&& r1==r2) return;
        for (int i = r1+1; i <= r2-1; i++) {
            res.add(matrix[i][l2]);
        }

        if(r1!=r2)
        for (int i = l2; i >=l1 ; i--) {
            res.add(matrix[r2][i]);
        }
        if(l1!=l2)
        for (int i = r2-1; i >= r1+1; i--) {
            res.add(matrix[i][l1]);
        }
        spiral(res,matrix,l1+1,l2-1,r1+1,r2-1);
    }
}

括号生成

在这里插入图片描述

  • 首先想到递归
  • 可以n个 和 n 个的全排序,然后再剔除无效 (效率太低)
  • 他和全排序的区别:只有两种不同的符号、需要按照一定规律,不可右括号先行
  • 可以用L 和 R 分别记录左右括号的剩余个数,他们的关系始终遵循L<=R,就不会出毛病!
  • 开始敲代码
     public  List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        String current = ""; // 记录当前拼接情况,初始化为空,也可以初始化为"("
        go(res,current,n,n);//开始递归
        return res;
    }
    public  void go(List<String> res,String cruuent ,int l,int r){
    	//出口
        if(l==0&&r==0){
            res.add(cruuent);
            return;
        }
        //始终遵循L<=R,所以他们不是 l==r 就是 l<r
        if(l==r){
            go(res,cruuent+"(",l-1,r);
        }else {
        	 //如果还有左括号,就加入左括号
            if(l>0)
                go(res,cruuent+"(",l-1,r);
            //加入右括号
            go(res,cruuent+")",l,r-1);
        }
    }

电话号码的字母组合(全排序)

在看别人代码的时候,不要以为看懂你就会敲了
自己敲一遍下来,以后类似的题目,做起来也也可以得心应手~

在这里插入图片描述
思路

这个和全排序有相似之处,顺便把全排序做了吧~
(递归大法好)

按照深度优先的方式,保存所有可能的结果。
在这里插入图片描述

class Solution {
   char [][] digitsLetter={{'a','b','c'},{'d','e','f'},{'g','h','i'},{'j','k','l'},{'m','n','o'}
   						,{'p','q','r','s'},{'t','u','v'},{'w','x','y','z'}};
    public List<String> letterCombinations(String digits) {
      List<String> res=new ArrayList<>(); //存储结果集
      if(digits.length()==0)return res;
      char[] currentRes=new char[digits.length()];//存储可能的排序
      letterComb(res,0,currentRes,digits);//递归
      return res;
    }
    public void letterComb(List<String> res,int i,char[] currentRes,String digits){
      boolean isLast=false;//判断是否是最后一个数字
      if(i==digits.length()-1) isLast=true;
      for (int j = 0; j < digitsLetter[digits.charAt(i)-50].length; j++) {
        currentRes[i]=digitsLetter[digits.charAt(i)-50][j];
        if(isLast){//最后一个数字,加入结果集
          StringBuilder stringBuilder=new StringBuilder();
          for (int k = 0; k <=i; k++) {
            stringBuilder.append(currentRes[k]);
          }
          res.add(stringBuilder.toString());
        }else//否则继续递归
          letterComb(res,i+1,currentRes,digits);
      }
    }
}

全排序

public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        if (nums == null || nums.length == 0) {
            return res;
        }
        permute(nums, 0, res);
        return res;
    }

    private void permute(int[] nums, int i, List<List<Integer>> res) {
        if (i == nums.length) {
            ArrayList<Integer> tmp = new ArrayList<>();
            for (int num : nums) {
                tmp.add(num);
            }
            res.add(tmp);
            return;
        }
        for (int j = i; j < nums.length; j++) {
            swap(nums, i, j);
            permute(nums, i + 1, res);
            swap(nums, i, j);
        }
    }

    private void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

四数之和

在这里插入图片描述
思路:在三数之和的基础上
我们已经做出来了三数之和,先指定一个数a,然后在定义左右指针寻找b,c。
那么四个数也可以这样做,指针i指定a以后,指针k每次指定d首先为最后一个数,然后指针l指向a的后一个数,指针r指向d的前一个数。

也就是在指针i内部加一个指针k循环,最内侧循环依然是l,r
在这里插入图片描述
注意:这时需要加一个去重条件,防止k指向大小相同的数。

while (k>i && nums[k - 1] == nums[k]) k--;

这样大体思路就完成了,来实现一下:

public List<List<Integer>> fourSum(int[] nums,int target) {
        List<List<Integer>> res=new ArrayList<>();
        if(nums.length==0)return res;
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 3; i++) {
            for (int k = nums.length-1; k > i+2; k--) {
                int target1 = target - nums[i]-nums[k];
                int l = i + 1, r = k- 1;
                if(nums[i]*2>target1 || nums[k]*2<target1)continue;
                while (l < r) {
                    if (nums[l] + nums[r] == target1) {
                        List<Integer> list = new ArrayList<>();
                        list.add(nums[i]);
                        list.add(nums[l]);
                        list.add(nums[r]);
                        list.add(nums[k]);
                        res.add(list);
                        l++;
                        r--;
                        while (l < r && nums[l] == nums[l - 1]) l++;
                        while (r > l && nums[r] == nums[r + 1]) r--;
                    } else if (nums[l] + nums[r] > target1) {
                        r--;
                    } else {
                        l++;
                    }
                }
                while (k>i && nums[k - 1] == nums[k]) k--;
            }
            while (i + 1 < nums.length && nums[i + 1] == nums[i]) i++;
        }
        return res;
    }

== 优化前执行结果==
在这里插入图片描述

优化
执行用时的误差还是比较小的,执行的结果还不是特别令人满意,我希望可以优化一下。优化无非希望他可以减少遍历不可能的答案,当已经定位了ad,我们便可以得出接下来获得的最大和、最小和。

最大和a+d*3<target ===那么l和r不再遍历
最小和a*3+d>target ===那么l和r不再遍历

那么优化其实只要再l,r遍历之前加上方框内的判断条件:
其中target1target减去ad
在这里插入图片描述

优化后执行结果
在这里插入图片描述

Z字形变换 (StringBuilder)

在这里插入图片描述

行数为3:
0		4		8
1 	3	5	7	9
2		6
行数为4:
0			6			12
1		5	7		11
2	4		8	10
3			9

分析上面的两个例子,可以发现其实有规律可循。

String res="";//结果
int n=s.length();
//l为两个N之间的距离,是一个固定值,N的那条斜线距离两竖距离是根据rows变化的,用l1记录。
int l=2*numRows-2,l1=0,p=0;//p为s的指针

实现1: 用String存储拼接结果

public String convert1(String s, int numRows) {
        String res="";
        int n=s.length();
        int l=2*numRows-2,l1=0,p=0;
        while(p<n){
            res+=s.charAt(p);
            p+=l;
        }
        for (int i = 1; i <numRows-1 ; i++) {
            l1+=2;
            p=i;
            while(p<n){
                res+=s.charAt(p);
                p+=l-l1;
                if(p<n){
                    res+=s.charAt(p);
                    p+=l1;
                }
            }
        }
        p=numRows-1;
        while(p<n){
            res+=(s.charAt(p));
            p+=l;
        }
        return res;
    }

通过是没问题的
在这里插入图片描述
在res字符串中,完全是进行字符串拼接操作。
但是了解StringBuilder的同学肯定知道,String是final修饰不可变的,每次修改值都会创建新的对象,大大加重了计算负担,而Stringbuilder和StringBuffer是在原有的值上进行修改不用创建新对象引用,大大提高了效率。

实现2: 用StringBuilder:快了好几倍。

public String convert(String s, int numRows) {
       if(numRows==1)return s;
       StringBuilder res=new StringBuilder();
       int n=s.length();
       int l=2*numRows-2,l1=0,p=0;
       while(p<n){
           res.append(s.charAt(p));
           p+=l;
       }
       for (int i = 1; i <numRows-1 ; i++) {
           l1+=2;
           p=i;
           while(p<n){
               res.append(s.charAt(p));
               p+=l-l1;
               if(p<n){
                   res.append(s.charAt(p));
                   p+=l1;
               }
           }
       }
       p=numRows-1;
       while(p<n){
           res.append(s.charAt(p));
           p+=l;
       }
       return res.toString();
   }

在这里插入图片描述

无重复字符的最长子串(滑动窗口)

在这里插入图片描述
第一思路:HashSet容器,以每个字符开头,遍历n-1次(字符串长度n),每次将元素加入容器,当有重复元素时喊停记录长度l,然后在清空容器,但是频繁的加入容器和清空消耗大量的时间。(打败全国5%的人T.T)

然后认识了一种叫滑动窗口法的方法:

定义两个指针,start和end,代表当前窗口的开始和结束位置,同样使用hashset,当窗口中出现重复的字符c且c的索引值>start时,start移动到容器中已有的c后面,没有重复时,end++,每次更新长度的最大值

从1000ms减到10ms,减少了频繁加入元素和清除容器的时间。这里参考下别人的优秀作业:

public int lengthOfLongestSubstring(String s) {
        int res = 0;
        int end=0,start=0;
       Map<Character,Integer> map=new HashMap<>();
        for(;end<s.length();end++){
           if(map.containsKey(s.charAt(end))){
               start=Math.max(map.get(s.charAt(end)),start);//从有重复的下一个位置继续找
           }     
            map.put(s.charAt(end),end+1);//map每次更新
            res=Math.max(res,end-start+1);//结果每次更新
        }
        return res;
    }

三数之和

在这里插入图片描述
要求:a+b+c=0,且三元组不重复。
简单起见,假设他们是一个有序数组,从第一个数开始作为a,target=-a,之后寻找b和c,令左指针指向a的下一个数b,右指针指向最后一个数c,while(l<r)

  • 如果b+c==target,加入结果集,两指针向中间靠拢,继续寻找答案。
  • 如果b+c<target,左指针右移
  • 如果b+c>target,右指针左移
  • 内部去重:如下图,此时nums[i]=-1,target=1,指针的当前位置已经符合条件,那么r继续左移不查重的话会导致答案重复,所以需要内部去重
    在这里插入图片描述
  • 外部去重:如图,当第一个 i 指向第一个 -1,并且已经找到所有target=1的情况了,那么i就没有必要再指向第二三个-1了,所以需要外部去重。
    在这里插入图片描述
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res=new ArrayList<>();
        if(nums.length==0)return res;
        Arrays.sort(nums);
        
        for (int i = 0; i < nums.length-2; i++) {
            int target=0-nums[i];
            int l=i+1,r=nums.length-1; //左指针和右指针
            while (l<r){
                if(nums[l]+nums[r]==target){//等于答案,加入res
                    List<Integer> list=new ArrayList<>();
                    list.add(nums[i]);
                    list.add(nums[l]);
                    list.add(nums[r]);
                    res.add(list);
                    l++;r--;
                    while (l<r&&nums[l]==nums[l-1]) l++;//内部去重
                    while (r>l&&nums[r]==nums[r+1]) r--;
                }else if(nums[l]+nums[r]>target){//大于目标 ,右指针往左
                    r--;
                }else l++; 小于目标 ,左指针往右
            }
             while (i+1<nums.length&&nums[i+1]==nums[i])i++;//外部去重
        }
        return res;
    }
}

两数之和

在这里插入图片描述
官方提示
第二个思路是,在不更改数组的情况下,我们可以以某种方式使用额外的空间吗? 像是散列表来加快搜索速度?

注意:

  • 元素可能重复
    输入:nums = [3,3], target = 6
    输出:[0,1]
  • 这边了解一下取值范围,不会超过int的21亿多
    2 <= nums.length <= 103
    -109 <= nums[i] <= 109
    -109 <= target <= 109
    只会存在一个有效答案

思路: 利用hashmap存储结构!遍历数组,每遍历一个元素nums[i],寻找与其匹配的t ( t = target - nums[i]) ,若存在hashmap.contains(t),则得出结论,否则,将num[i],i put 进 hashmap

public int[] twoSum(int[] nums, int target) {
        int [] res = new int[2];
        HashMap<Integer,Integer> hashMap = new HashMap();
        for (int i = 0; i < nums.length; i++) {
            int t = target - nums[i];
            if (hashMap.containsKey(t)){
                res[0] = hashMap.get(t);
                res[1] = i;
                return res;
            }else {
                hashMap.put(nums[i],i);
            }
        }
        return res;
    }

跳台阶【dp】

在这里插入图片描述
可以采用dp思想:可以采用以下例子,但是会有重复的计算,比如需要重复计算f3的值
f5 = f4 + f3
= (f4+ f3) + f3
=((f3+f2)+f3)+f3…

 public int JumpFloor(int target) {
        if(target == 1) {
            return 1;
        }
        if(target == 2){
            return 2;
        }
        return JumpFloor(target-1)+JumpFloor(target-2);
    }

改进:将结果存储起来,下次需要直接获取。

public static int JumpFloor(int target) {
        if(target <3) return target;
        int []a = new int[target+1];
        a[1] = 1;
        a[2] = 2;
        for(int i = 3;i<= target;i++){
            a[i] = a[i-1]+a[i-2];
        }
        return a[target];
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值