面试题42、43、44、45

面试题42.连续子数组的最大和

在这里插入图片描述

  • 状态定义:dp[i] 代表以元素 nums[i] 为结尾的连续子数组的最大和

  • 状态转移方程:若 dp[i - 1] <= 0,说明 dp[i-1] 对dp[i] 产生负贡献,即 dp[i-1] + nums[i] 还不如 nums[i] 本身大。

    • 当 dp[i−1] > 0 时:执行 dp[i] = dp[i−1] + nums[i];
    • 当 dp[i−1] ≤ 0 时:执行 dp[i] = nums[i];
  • 初始状态:dp[0] = nums[0],即以 nums[0] 结尾的连续子数组最大和为 nums[0]。

  • 返回值:返回 dp 数组中的最大值,代表全局最大值

在这里插入图片描述

class Solution {
    public int maxSubArray(int[] nums) {
        int len = nums.length;
        if(len < 1) return 0;
        int[] dp = new int[len];
        dp[0] = nums[0];
        int max = dp[0]; //记录dp数组中的最大值
        for(int i = 1; i < len; i++) {
            if(dp[i - 1] <= 0) dp[i] = nums[i];
            else dp[i] = nums[i] + dp[i - 1];
            max = Math.max(max, dp[i]);
        }
        return max;
    }
}
  • 时间复杂度 O(N) : 线性遍历数组 nums 即可获得结果,使用 O(N) 时间。
  • 空间复杂度 O(N) : 使用原数组大小的额外空间

优化
  • 由于 dp[i] 只与 dp[i−1] 和 nums[i] 有关系,因此可以将原数组 nums 用作 dp 列表,即直接在 nums 上修改即可。
  • 由于省去 dp 列表使用的额外空间,因此空间复杂度从 O(N) 降至 O(1) 。
class Solution {
    public int maxSubArray(int[] nums) {
        int len = nums.length;
        if(len < 1) return 0;
        int res = nums[0];
        for(int i = 1; i < len; i++) {
        	nums[i]  = nums[i] + Math.max(nums[i - 1], 0);
        	res = Math.max(res, nums[i]);
        }
        return res;
    }
}
  • 若题目要求不能修改原有数组,考虑到在dp列表中,dp[i] 只和 dp[i-1] 有关,所以用两个参数存储循环过程中的 dp[i] 和 dp[i-1] 的值即可,空间复杂度也为O(1)。
class Solution {
    public int maxSubArray(int[] nums) {
        int len = nums.length;
        if(len < 1) return 0;
        int res = nums[0];
        int pre = 0; //用于记录dp[i-1]的值,对于dp[0]而言,其前面的dp[-1]=0
        int cur = nums[0]; //用于记录dp[i]的值
        for(int num : nums) {
        	cur = num;
        	if(pre > 0) cur += pre;
        	if(cur > res) res = cur;
        	pre = cur; 
        }
        return res;
    }
}

———————————————————————————————————————

面试题43.1~n 整数中出现 1 的次数

在这里插入图片描述
假设 f(n) 函数,意思是1~n 这n个整数的十进制表示中1出现的次数,将 n 拆分为两部分,最高一位的数字high和其他位的数字last,分别判断情况后将结果相加。

例子一:如 n=1234,high=1,pow=1000,last=234

可以将数字范围分为两部分 1 ~ 999 和 1000 ~ 1234

  • 1 ~ 999 这个范围 1 的个数是 f(pow - 1)
  • 1000~1234这个范围1的个数需要分为两部分:
    • 千分位是1的个数:千分位为1的这个数字的个数刚好就是234+1(last+1),注意,这儿只看千分位,不看其他位
    • 其他位是1的个数:即是234中出现1的个数,为f(last)

所以全部加起来是f(pow-1) + last + 1 + f(last);

例子二:如3234,high=3,pow=1000,last=234

可以将数字范围分成几部分 1 ~ 999,1000 ~ 1999,2000 ~ 2999 和 3000 ~ 3234

  • 1 ~ 999这个范围 1 的个数是 f(pow-1)
  • 1000 ~ 1999这个范围 1 的个数需要分为两部分:
    • 千分位是 1 的个数:千分位为1数字的个数刚好就是 pow,注意,这儿只看千分位,不看其他位
    • 其他位是 1 的个数:即是999中出现 1 的个数,为 f(pow-1)
  • 2000 ~ 2999这个范围 1 的个数是 f(pow-1)
  • 3000 ~ 3234这个范围 1 的个数是 f(last)

所以全部加起来是pow + high*f(pow-1) + f(last);

class Solution {
    public int countDigitOne(int n) {
        return f(n);
    }
    //1~n 这n个整数的十进制表示中 1 出现的次数
    private int f(int n) {
		if(n <= 0) return;
		String s = String.valueOf(n);
		int high = s.charAt(0) - 0;
		int pow = (int)Math.pow(10, s.length() - 1);
		int last = n - high * pow;
		if(high == 1) {
			return f(pow - 1) + last + 1 + f(last);
		} else {
			return pow + high * f(pow - 1) + f(last);
		}
    }
}

———————————————————————————————————————

面试题44.数字序列中某一位的数字

在这里插入图片描述
0 占第0位
1~9 共 9x1 = 9 个数字,占位 9x1 = 9 位
10~99 共 9x10 = 90 个数字,占位 90x2 = 180 位
100~999 共 9x100 = 900 个数字,占位 900x3 = 2700 位

算法的逻辑就是:依次算一下占位数字,并不断地累加得到当前的总占位数,并判断和输入n的关系,总占位数小于n,说明第n位不在目前的范围內,继续累加;否则,说明在范围,然后找到相应数字返回。

举个例子:

假设输入n为14,我们想找到第14位:

  • (1) 此时设置当前位置为 0 的位置 temp=0

  • (2) 占 1 位的数字有 9 个:num=9, (1~9除了0,因为 temp 已设为 0 了)

  • (3) 占1位 base=1

  • (4) 当占1位的数字都走完了,目前一共占到了多少位: temp + num x base = 0 + 9 x 1 = 9,说明占1位的数字走完后,当前占到了第9位。(更新temp=temp + num x base = 9)

  • (5) 和输入的值比较下,9 < 14,说明我们想找的第 14 位不在当前占 1 位的数字中。

  • (6) 那就有可能在占 2 位的数字中,所以这一轮我们看看占2位的数字(10~99):

    • 每个数字占位 base = base + 1 = 2
    • 有多少个数字 num = num x 10 = 90
    • 再回到第(4)步,算一下当占2位的数字也都走完了,目前一共占到了多少位:temp + num x base = 9 + 90 x 2 = 189,说明当占2位的数字走完后,当前占到了第189位
    • 再回到第5步,发现 189 > 14,说明我们想找到的数字就在10~99之间
    • 此时,循环终止…因为没必要再往下算占3位的情况了
  • (7) 我们知道第14位就在10~99之间的话就很好办了:

    • 前一轮我们知道占1位的数字走完后,占到了第9位,那我们想找的第14位的值也就是9之后的第5位:14 - 9 = 5位
    • 占两位的数字中(10~99),第一个起始数字是10,(10 = 10的1次方,也就是10(base-1)
    • 由于10~99这个范围内的数字,都是占base=2位,所以 5/2 = 2,10 + 2 = 12,第14位就在数字12里;5%2 = 1,说明第14位就是数字12中的第一个位置值,如果把12当成字符串,那就是下标为0的值“12”.charAt(1-1) = 1
class Solution {
    public int findNthDigit(int n) {
		if(n < 10) return n;
		long m = n; //将 n 转换为 long 类型,防止大数越界
		long temp = 0; //表示当前占到第几位,从0开始
		long base = 1; //表示当前所处的区间里,数字是几位的(1~9,base=1;10~99,base=2等等)
		long num = 9; //表示现在区间内多少个数字
		char res = '0';
		//查找n处于哪个数字区间里
		//num*base 表示这个区间里的数字有num个,每个数字的位数都是base,相乘就是区间里数字总位数
		while(temp + num * base < m) {
			temp += base * num;
			base += 1;
			num *= 10;
		}
		//m-temp 表示题目所给数字到这个区间首数字有多少位
		//base表示这个区间每个数有多少位
		//两者相除表示之间大概有多少个数
		long a = (m - temp) / base;
		long b = (m - temp) % base; //是否处在一个完整数的末尾
		if(b != 0) {
			long c = (long) (Math.pow(10, base - 1) + a);
			res = String.valueOf(c).charAt((int)b - 1);
		} else {
			long c = (long) (Math.pow(10, base - 1) + a - 1);
			res = String.valueOf(c).charAt((int)base - 1);
		}
		return res - '0';
    }
}

———————————————————————————————————————

面试题45.把数组排成最小的数

在这里插入图片描述
设数组 nums 中任意两数字的字符串为 x 和 y,则规定排序判断规则为:

  • 若拼接字符串 x+y > y+x ,则 x “大于” y ;
  • 反之,若 x + y < y + x,则 x “小于” y ;

在这里插入图片描述

1、初始化:字符串列表 strs,保存各数字的字符串格式;

2、列表排序: 应用以上 “排序判断规则” ,对 strs 执行排序;

3、返回值: 拼接 strs 中的所有字符串,并返回。

class Solution {
    public String minNumber(int[] nums) {
		String[] strs = new String[nums.length];
		for(int i = 0; i < nums.length; i++) {
			str[i] = String.valueOf(nums[i]);
		}
		Arrays.sort(strs, (x, y).compareTo(y + x));
		StringBuilder sb = new StringBuilser();
		for(String s : strs) {
			sb.append(s);
		}
		return sb.toString();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值