常见算法面试题

一个数组,求连续最大的和

给定一个数组,求出其中连续的最大的和

问题举例

 数组 {1,-4,10,5,-10,23}, 求出当前数组中最大的和,10+5+ -10 +23 = 28
 数组 {2,-10,4,9,-29,10}, 求出当前数组中最大的和,4+9=13

问题解析

已知数组,想求出连续最大和,先初始化最大值为数组第一个数字;
然后循环数组一直相加求连续和,每加完一个值,就用连续和  和 最大值进行比较,从新赋值;
求连续和的过程中,如果出现负数,则清0,原因,负数和后一个相加,肯定没有0和后一个相加数字大;

代码

public int getSum(int[] arr){
	if(arr.length == 0)
		return 0;
	//用来记录最大和的值	
	int max = arr[1];
	//用来记录数组连续和
	int sum = 0;
	for(int i : arr){
		//计算 arr[0]+arr[1]+arr[2]+....  出现负数归零
		sum = Math.max(0,sum+i);
		//比较 arr[0]+arr[1]+arr[2]+.... 和当前最大值谁大
		max = Math.max(sum,max);
	}
	return max;
}

整数反转

将一个整数进行反转输出

问题举例

数字 1234,反转后为 4321;
数字 -1230,反转后为 -321;

问题解析

整数反转,可能会带来一个问题,就是反转后的数字超过了int类型的最大或最小值,代码多加一行判断,这种情况暂时不考虑;
进行反转,符号保留,那就从被反转整数的最后一个数字开始计算,计算方式为取模的方式

代码

public int reverse(int x){
	//用来记录反转后的整数
	int r = 0;
	while(x != 0){
		//x%10 取出原整数的最后一位数
		//r*10 是为了将反转后的数进位   3变成30    31变成310
		r = (r*10)+(x%10);
		//原整数去掉最后一位数字
		x/=10;
	}
	return r; 
}

回文数

回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数

问题举例

数字 1234,从后向前读4321,不是回文数;
数字 12321,从后向前读也是 12321,这是个回文数;
数字 1221,从后向前读也是 1221,这是个回文数;
数字 -121,从后向前读也是 121-,不是回文数;

问题解析

由回文数的本质可知,负数不能成文回文数,10的倍数不能成为回文数(10,40,3000);
判断回文数分为两种,一种是长度为偶数的整数,从中间分开,正好两边长度相同的数字去比较;
还有一种是长度为基数的,中间分开后,最中间的数据去掉,两边的数字去比较;

代码

public boolean isPalindrome(int x) {
	if(x < 0 || (x != 0 && (x % 10) == 0))
		return false;
	//记录反转后的数字
	int r = 0;
	
	while(r < x){
		//x%10 取出原整数的最后一位数
		//r*10 是为了将反转后的数进位   3变成30    31变成310
		r = (r*10)+(x%10);
		//原整数去掉最后一位数字
		x/=10;
	}
	//x==r/10  是因为  121  这种奇数位数据,x最后结果为 12,除10 之后  变为1,在进行判断
	return x==r ? true : false || x==r/10 ? true : false;
}

删除排序数组中的重复项

给定一个有序数组,在当前数组中直接删除重复项,使每个元素只出现一次(删除并不准确,应为替换),并返回新数组长度

问题举例

[1,2,2,3,4,4,5]  删除重复项后,原数组变为 [1,2,3,4,5,4,5] ,返回长度为 5(原数组前5个元素为新数组,且元素不重复);   
[1,2,2,5]  删除重复项后,原数组变为 [1,2,5,5] ,返回长度为 3(原数组前3个元素为新数组,且元素不重复);

问题解析

在原数组上直接做修改,是数组前面的元素不重复,后面的数据就不管了,然后返回前面不重复元素的个数;
数组是排好序的,

代码

public int removeDuplicates(int[] nums)  {
	if(nums == null || nums.length == 0){
		//数组无元素的情况
		return 0;
	}
	//数组的第一个元素,用于和后面的比较是否元素相等,如果不相等
	int startNum = nums[0];
	//替换后的数组长度,从第二个数组开始循环,所以新数组长度初始为1
	int newLen = 1;
	//循环第一个元素肯定不会涉及重复问题,所以从第二个元素开始循环
	for(int i=1;i<nums.length;i++){
		if(nums[i] != startNum){
			nums[newLen] = nums[i];
			//新数组长度加1
			newLen ++;
			//从新赋值,下一次比较重复元素使用
			startNum = nums[i];
		}
	}
	return newLen;
}

完美数

一个 正整数,如果它和除了它自身以外的所有 正因子 之和相等,我们称它为 完美数。
给定一个 整数 n, 如果是完美数,返回 true,否则返回 false

问题举例

输入6,能被28整除的整数有1、2、4、7、14,1+2+4+7+14=28,与输入数相同,返回true
输入15,能被15整除的整数有1、3、5,1+3+5 与输入数不同,返回false

问题解析

通过题目可知,整数1的正因子只有它本身,所以输入1的情况直接返回false
所有正整数都可被1整除
一个数除被一个数整除,肯定也能被得出的商整除,6/2 =3,反正 6/3=2,所以整除时商和除数都是因子
整数除以最小的正因子,那么商就是它最大的正因子。
	假设2位最小正因子。输入18,  18/2=9, 那么9就是18的最大因子(题目不包括自身,所以除数是2开始的)

代码

class Solution {
    public boolean checkPerfectNumber(int num) {
    	//等于1直接返回false
        if(num == 1)
            return false;
        //用来记录正因子之和,因为所有正整数都可被1整除,默认初始值为1
        int total = 1;
        //记录因子的初始值,此处没用1,因为total默认为1了
        int y = 2;
        //临时变量
        int x = num;
        while(y < x){
        	//判断是否整除正因子
            if(num % y == 0){
            	//能被整除的情况,记录当前除数和商的和
                total = (total + y) + (num / y);
                //临时变量记录为当前的商,因为最大的因子只会是这个商
                x = num / y;
            }
            y++;
        }
        return total == num;
    }
}

替换所有的问号

一个仅包含小写英文字母和 ‘?’ 字符的字符串 s,请你将所有的 ‘?’ 转换为若干小写字母,使最终的字符串不包含任何 连续重复 的字符。

问题举例

输入 ?zs,
			返回 azs,该示例共有 25 种解决方案,从 "azs" 到 "yzs" 都是符合题目要求的。
			只有 "z" 是无效的修改,因为字符串 "zzs" 中有连续重复的两个 'z' 。
输入ubv?w
			返回ubvaw,该示例共有 24 种解决方案,只有替换成 "v" 和 "w" 不符合题目要求。
			因为 "ubvvw" 和 "ubvww" 都包含连续重复的字符

问题解析

如果输入的s==? ,可以直接返回任意一个字母
如果?在输入字符串的第一个或者最后一个位置,只需要比较前一个或者后一个字符就可,例如 ?a 和 a?
如果?在输入字符串的第一个或者最后一个位置,记录前一个或者后一个字符要判断是否会下标越界

代码

class Solution {
    public static String modifyString(String s) {
        if(s.equals("?"))
        	//输入的是字符串是问号,直接返回字母a
            return "a";
        //将输入的字符串转换为char数组
        char[] ss = s.toCharArray();
        //循环转换的char数组
        for(int index =0; index < ss.length;index++){
            if(ss[index] != '?'){
            	//如果当前不是 ?直接下一次循环
                continue;
            }
            //记录当前 ? 的前一个和后一个字符,默认给一个 *
            char start = '*',end = '*';
            //如果当前 ? 处于第一个字符的位置,只需要比较后一个字符
            if(index == 0 && s.length()-1 != index){
            	//记录后一个字符
                end = ss[index + 1];
            } 
            //如果当前?处于最后一个字符的位置,只需要比较前一个字符
            else if(index == s.length()-1 && index != 0){
            	//记录前一个字符
                start = ss[index - 1];
            }
            //如果当前?在字符串的中间位置,记录前后字符
            else{
            	//记录前一个字符 和 后一个字符
                start = ss[index - 1];
                end = ss[index + 1];
            }
            a:
            for(char ch = 'a';ch < 'z'; ch ++){
            	//循环a-z字母,判断前后字符都不等于当前循环的字母
                if(ch != start && ch != end){
                	//将当前问号替换字母,并跳出当前循环
                    ss[index] = ch;
                    break a;
                }
            }
        }
        return new String(ss);
    }
}

简化路径

给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 ‘/’ 开头),请你将其转化为更加简洁的规范路径
在 Unix 风格的文件系统中:一个点(.)表示当前目录本身;两个点 (…) 表示将目录切换到上一级(指向父目录);任意多个连续的斜杠(即,‘//’)都被视为单个斜杠 ‘/’ 。

问题举例

输入/home/,返回 /home
			目录最后的 / 可以省略不展示
输入/home//foo/,返回 /home/foo
			路径中间的双斜杠,可以简化为单斜杠,并将最后的斜杠省略
输入/a/./b/../../c/,返回 /c
			路径/a/.中的 (.),表示当前路径,也就是/a路径,所以路径变为/a/b/../../c/
			路径/a/b/..中的(..)表示上级目录,所以路径变为/a/../c/
			路径/a/..中的(..)表示上级目录,路径变为/c/,最终简化为/c
输入/home/../../..,返回 /

问题解析

可以将路径用/分割成为一个数组,判断每一个元素,是否包括(.)或者(..),分别做不同的处理
创建一个数组(用栈更优),记录原路径数组循环时简化后的路径
	长度为2倍的原路径分割的数组长度,因为原数组分割后的长度是不包含斜杠的了,但是优化的数组是要包含斜杠的

代码

class Solution {
    public static String simplifyPath(String path) {
        //斜杠分割转数组
        String[] arrPath = path.split("\\/");
        //用于记录简化后的数据,长度*2是因为简化后的数据会包含 斜杠
        String[] arr = new String[arrPath.length*2];
        //用于记录记录简化后路径数组的已经使用的数组下标
        int index = -1;
        //循环遍历数组中的元素
        for(String str : arrPath){
            //如果是. 指向当前目录,什么也不用干,进入下次循环;
            //如果是.. 并且下标为-1 虽然指向上一个目录,但是下标为-1说明还没有路径,什么也不用干,进入下次循环
            if(str == null || "".equals(str) || ".".equals(str) || (index == -1 && "..".equals(str))){
                //如果当前元素为空,则进入下次循环
                continue;
            }

            if ("..".equals(str)){
                //如果是..  则指向上一个目录,删除最后一个路径已经路径前的斜杠,并将下标恢复
                arr[index] = "";
                arr[index-1] = "";
                index -=2;
            }else{
                //如果不是.. 不是.  则正常添加路径和路径前的斜杠,下标+2
                arr[index+1] = "/";
                arr[index+2] = str;
                index +=2;
            }

        }
        //记录最终的简化路径
        StringBuilder sb = new StringBuilder();
        for(String str :arr){
            //如果出现一次空,就说明路径已经简化结束,直接退出循环即可
            if(str == null|| "".equals(str)){
                break;
            }
            //记录简化路径
            sb.append(str);
        }
        //如果路径为空,直接返回斜杠
        return sb.toString().equals("") ? "/" : sb.toString();
    }
}

递增的三元子序列

一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。
如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false
注意:i,j,k 不用必须连续

问题举例

输入[1,2,3,4,5],返回true:下标0,1,2位置的数字是一次递增的
输入[2,1,5,0,3],返回false: 不存在长度为3的递增子序列
输入[1,5,3,4],返回true: 下标0,2,3位置的数字是一次递增的

问题解析

通过题目可知,数组长度如果小于3,那必然是返回false
思路:
	先定位到序列的第一个数,可以让这个数默认等于数组的第一个数
	序列的第二个数默认值为 最大整数,防止循环比较时,直接返回true
	循环数组,判断当前循环的数和第一个数、第二个数的关系:
		如果 第一个数 < 第二个数 < 当前数 ,return true
		如果 第一个数 < 第二个数 &&   第二个数 > 当前数 ,第二个数从新赋值
		如果 第一个数 < 当前数,第一个数从新赋值

代码

class Solution {
    public static boolean increasingTriplet(int[] nums) {
    	//获取数组长度
    	int length = nums.length;
        if(length < 3){
        	//如果小于3,直接返回false
            return false;
        }
        //子序列第一个值,默认为数组第一个数字
        int fir = nums[0];
        //子序列第二个值,默认为最大整数
        int sec = Integer.MAX_VALUE;
		//数组循环下标
        int i = 1;
        while(i < length){
        	//循环的当前数值
            int num = nums[i];
            
            if(sec < num){
            	//如果第二个数,小于当前数,返回true. 因为第一个数,小于第二个数
                return true;
            }else if(fir < num){
            	//进入这个条件,说明第一个条件没通过,就是前提条件为 第二个数,小于当前数
            	//第二个数,小于当前数 并且 第一个数小于当前数,就将当前数赋值给第二个数
                sec = num;
            }else{
            	//进入这个条件,说明第一个数肯定是大于当前数的,将第一个数从新复制
                fir = num;
            }
            //进入下一次循环
            i++;
        }
        return false;
    }
}

至少是其他数字两倍的最大数

一个整数数组 nums ,其中总是存在 唯一的 一个最大整数 。
并且这个数 至少是数组中每个其他数字的两倍 。
如果是,则返回 最大元素的下标 ,否则返回 -1

问题举例

输入[3,6,1,0],返回1:下标1位置的数字是最大,并且满足是数组中其他数字最少两倍的条件
输入[1,2,3,4],返回-1: 下标3位置的数字是最大,但是不满足条件,返回-1
输入[9],返回0: 不存在其他数字,默认为其他数字两倍

问题解析

如果输入数组长度为1,那么直接返回0即可
先找出数组中最大的数字,这个最大的数字要和数组中其他数字的二倍进行比较大小,有这个可以得知:
	我们用最大的数字,和第二大的数字的二倍比较大小就可以了
	如果最大的数字比第二大的数字的二倍大,那就一定比其他的数字的二倍大
	第二大的数字的二倍肯定也比其他数字的二倍大

代码

class Solution {
    public static int dominantIndex(int[] nums) {
        if(nums.length == 1){
            //数组长度为1,直接返回
            return 0;
        }
        //数组前两个数字中,较大数字的下标
        //用于存放最大数字的下标
        int max = nums[0] > nums[1] ? 0 : 1;
        //数组前两个数字中,较小数字的下标
        //用于存放第二大数字的下标
        int prev = max == 0 ? 1 : 0;

        //因为上面已经取出来前两个下标的数,所以循环从第三个开始
        for(int i =2;i<nums.length;i++){

            if(nums[max] < nums[i]){
                //判断最大数字小于当前这个数,那就把大数进行替换
                prev = max;
                max = i;
            }else if(nums[prev] < nums[i]){
                //判断最大数字大于当前这个数,并且第二大的数小于当前这个数,替换第二大的数字
                prev = i;
            }
        }
        //判断最大数字否大于第二大数字的两倍
        return nums[max] >= nums[prev] * 2 ? max : -1;
    }
}

最小时间差

给定一个 24 小时制(小时:分钟 “HH:MM”)的时间列表,找出列表中任意两个时间的最小时间差并以分钟数表示。

问题举例

输入["23:59","00:00"],返回1,相差1分钟
输入["00:00","23:59","00:00"],返回0,有相同时间,相差分钟数为0

问题解析

如果列表中,有两个相同时间,则直接返回0
如果输入列表的长度为 60分钟*24小时=1440个数据,那就说明必然会有重复数据,直接返回0
数据比较时间时,还要判断最大时间和最小时间的 24 小时差:
	输入["00:00","01:00","23:50"]  ,不止要正着比较 各个时间差,还要倒着比较
	比如:00:00到和23:50,正着比较就是 23小时50分,但是倒着比较却只有10分钟 

代码

class Solution {
    public static int findMinDifference(List<String> timePoints) {

        //获取输入的集合长度
        int timePointsLen = timePoints.size();
        if (timePointsLen > 1440) {
            //如果长度大于1440,说明肯定会有重复的时间,直接返回
            return 0;
        }
        //新创建一个数组,用于记录传过来的集合的分钟时
        int[] timeMArr = new int[timePointsLen];
        //循环传过来的集合
        for(int i = 0;i < timePointsLen;i++){
            //获取每一时间的值
            String time = timePoints.get(i);
            //将时间换算成从0点到当前的分钟数,时乘60+分  例: 10:15   10*60+15
            timeMArr[i] = Integer.valueOf(time.substring(0,2))*60 + Integer.valueOf(time.substring(3,5));
        }
        //将时间分钟数的集合排序,排序后,分钟从小到大
        Arrays.sort(timeMArr);
        //当前最小的时间,初始化为第二个和第一个分钟数的差
        int min = timeMArr[timePointsLen-1] - timeMArr[timePointsLen-2];
        //循环记录分钟数的数组
        for(int len =1;len < timePointsLen ;len++){
            //判断后一个分钟和前一个分钟的差,和默认的时间差进行比较,取最小
            min = Math.min(timeMArr[len] - timeMArr[len-1],min);
            if(min == 0){
                //如果出现两个时间的差等于0的情况,直接返回0
                return min;
            }
        }
        //倒着比较最小和最大的两个时间的差,加1440就是加24小时的分钟数
        int m = timeMArr[0] + 1440 - timeMArr[timePointsLen-1];
        //输出最小的
        return min > (m) ? m : min;
    }
}

存在重复元素 II

一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j
满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false

问题举例

 nums = {1,2,3,1}, k=3, 返回true。下标0和下标3的数字相同并且abs(0-3)<=k
 nums = [1,0,1,1], k = 1, 返回true。下标2和下标3的数字相同并且abs(2-3)<=k
 nums = [1,2,3,1,2,3], k = 2, 返回false。存在下标相等的数字,但是下标差的绝对值>k

问题解析

可以使用map来记录每个数字出现的下标位置
循环数组,当发现map中已经出现了当前的数字,说明此时已经满足了题目的 nums[i] == nums[j] 条件
发现满足 nums[i] == nums[j] 条件时,只需要在判断 abs(i - j) <= k 条件是否成立

代码

public static boolean containsNearbyDuplicate(int[] nums, int k) {
	//存放每个数据出现位置
    Map<Integer,Integer> map = new HashMap<>();
    //循环数组
    for(int i = 0;i<nums.length;i++){
    	//当前循环出的数字
        Integer num = nums[i];
        //用当前数字,从map中取出出现在位置
        Integer index = map.get(num);

        if(index != null && i - index <= k ){
        	//map中存在当前数字,并且上次记录的下标和这次出现的下标差<=k,直接返回true
            return true;
        }else{
        	//map中不存在当前数字,或者下标已经不符合条件了,添加记录
            map.put(num,i);
        }
    }
    return false;
}

写字符串需要的行数

把给定的字符串 S 从左到右写到每一行上,每一行的最大宽度为100个单位,如果在写某个字母的时候会使这行超过了100 个单位,那么应该把这个字母写到下一行。
给定了一个数组 widths ,这个数组 widths[0] 代表 ‘a’ 需要的单位, widths[1] 代表 ‘b’ 需要的单位,…, widths[25] 代表 ‘z’ 需要的单位。

现在回答两个问题:至少多少行能放下S,以及最后一行使用的宽度是多少个单位?将答案作为长度为2的整数列表返回。

问题举例

widths =[90,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,40]
S = "azz"
输出: [2, 80]
解释: 
字符串'a'在数组下标第一个位置,也就是长度为90,'z'的长度为40,因为一行的宽度为100,所以‘a’自己在一行,'zz'另起一行,总共2行,第二行为‘zz’宽度为80

问题解析

字符串从左到右写到每一行上
每一行宽度不能超过100,写字符时如果宽度超过100,当前字符另起一行
字符串中每一个字符的宽度,在数组中有对应,字符‘a’‘b’‘c’‘d’,分别对应数组中0,1,2,3的下标,以此类推,‘z’在数组中最后的一个元素

代码

	public int[] numberOfLines(int[] widths, String S) {
        int[] ans = new int[2];
        int l = 0,r = 1;
        for(int i=0;i<S.length();i++){
            l += widths[S.charAt(i)-'a'];
            if(l > 100){
                r += 1;
                l = widths[S.charAt(i)-'a'];
            }
        }
        ans[0] = r;
        ans[1] = l;
        return ans;
    }

未完待续

还会持续更新一些常见得面试算法
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值