面试题46、47、48、49

面试题46.把数字翻译成字符串

在这里插入图片描述
本题使用动态规划
在这里插入图片描述
1、状态定义:设动态规划列表 dp,dp[i] 代表以 xi 为结尾的数字的翻译方案数量

2、状态转移方程:若 xi 和 xi-1 组成的两位数字可以被翻译,则 dp[i] = dp[i-1] + dp[i-2];否则 dp[i] = dp[i-1]。

  • 可被翻译的两位数:当 xi-1 = 0,组成的两位数是无法被翻译的(例如 00, 01, 02,⋯ ),因此区间为 [10, 25]。

3、初始状态:dp[0] = dp[1] = 1,即无数字和第1位数字的翻译方法数量均为1;

为什么 dp[0] = 1?
当 num 第 1, 2 位的组成的数字 ∈[10,25] 时,显然应有 2 种翻译方法,即 dp[2] = dp[1] + dp[0] = 2,而显然 dp[1] = 1,因此推出 dp[0] = 1 。

方法一:字符串遍历

  • 为方便获取数字的各位 xi,考虑先将数字 num 转化为字符串 s ,通过遍历 s 实现动态规划。
  • 通过字符串切片 s[i - 2:i] 获取数字组合 10xi-1 + xi,通过对比字符串 ASCII 码判断字符串对应的数字区间。
class Solution {
    public int translateNum(int num) {
        String s = String.valueOf(num);
        int[] dp = new int[s.length() + 1];
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 1; i < s.length(); i++) {
            if(s.charAt(i - 1) == '1' || (s.charAt(i - 1) == '2' && s.charAt(i) < '6')) {
                dp[i + 1] = dp[i] + dp[i - 1];
            } else {
                dp[i + 1] = dp[i];
            }
        }
        return dp[dp.length - 1];
    }
}
优化

由于 dp[i] 只与 dp[i−1] 有关,因此可使用两个变量 cur,pre 分别记录 dp[i],dp[i−1] ,两变量交替前进即可。此方法可省去 dp 列表使用的 O(N) 的额外空间。

class Solution {
    public int translateNum(int num) {
		String s = String.valueOf(num);
		int pre = 1; //替代dp[i-1]
		int cur = 1; //替代dp[i]
		for(int i = 2; i <= s.length(); i++) {
			String str = s.substring(i - 2, i);
			int tmp = 0;
			if(str.compareTo("10") >= 0 && str.compareTo("25") <= 0) {
				tmp = pre + cur;
			} else {
				tmp = cur;
			}
			pre = cur;
			cur = tmp;
		}
		return cur;
    }
}
  • 时间复杂度 O(N) : N 为字符串 s 的长度(即数字 num 的位数 log(num) ),其决定了循环次数。
  • 空间复杂度 O(N) : 字符串 s 使用 O(N) 大小的额外空间。

方法二:数字求余

  • 利用求余运算 num%10 和求整运算 num/10 ,可获取数字 num 的各位数字(获取顺序为个位、十位、百位…)。
  • 因此,可通过 求余 和 求整 运算实现 从右向左 的遍历计算。而根据上述动态规划 “对称性” ,可知从右向左的计算是正确的。
  • 自此,字符串 s 的空间占用也被省去,空间复杂度从 O(N) 降至 O(1) 。
class Solution {
    public int translateNum(int num) {
		int pre = 1, cur = 1;
		int x, y = num % 10;
		while(num != 0) {
			num /= 10;
			x = num % 10;
			int tmp = 10 * x + y;
			int c = (tmp >= 10 && tmp <= 25) ? pre + cur : cur;
			pre = cur;
			cur = c;
			y = x;
		}
		return cur;
    }
}
  • 时间复杂度 O(N) : N 为字符串 s 的长度(即数字 num 的位数 log(num) ),其决定了循环次数。
  • 空间复杂度 O(1) : 几个变量使用常数大小的额外空间。

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

面试题47.礼物的最大价值

在这里插入图片描述
题目说明:从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。
根据题目说明,易得某单元格只可能从上边单元格或左边单元格到达

设 f(i, j) 为从棋盘左上角走至单元格 (i,j) 的礼物最大累计价值,易得到以下递推关系:f(i,j) 等于 f(i,j−1) 和 f(i-1,j) 中的较大值加上当前单元格礼物价值 grid(i,j) :

  • f(i,j) = max[f(i,j−1),f(i−1,j)] + grid(i,j)
    在这里插入图片描述
    1、状态定义:设动态规划矩阵 dp ,dp(i,j) 代表从棋盘的左上角开始,到达单元格 (i,j) 时能拿到礼物的最大累计价值。

2、转移方程:

  • 1.当 i = 0 且 j = 0 时,为起始元素;
  • 2.当 i = 0 且 j ≠ 0 时,为矩阵第一行元素,只可从左边到达;
  • 3.当 i ≠ 0 且 j = 0 时,为矩阵第一列元素,只可从上边到达;
  • 4.当 i ≠ 0 且 j ≠ 0 时,可从左边或上边到达;

3、初始状态: dp[i][0] = grid[0][j] = 0,,为了方便考虑边界条件,首行和首列均为0

class Solution {
    public int maxValue(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] dp = new int[m + 1][n + 1];
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];
            }
        }
        return dp[m][n];
    }
}
  • 时间复杂度 O(MN) : M, N 分别为矩阵行高、列宽;动态规划需遍历整个 grid 矩阵,使用 O(MN) 时间。
  • 空间复杂度 O(MN) : 使用行数*列数大小的额外空间。

优化

使用一维数组来存储元素

class Solution {
    public int maxValue(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[] dp = new int[n + 1];
        for(int i = 0; i < m; i++) {
            for(int j = 1; j <= n; j++) {
                dp[j] = Math.max(dp[j - 1], dp[j]) + grid[i][j - 1];
            }
        }
        return dp[n];
    }
}
  • 时间复杂度 O(MN) : M, N 分别为矩阵行高、列宽;动态规划需遍历整个 grid 矩阵,使用 O(MN) 时间。
  • 空间复杂度 O(N) : 使用列数大小的额外空间。
    ———————————————————————————————————————————

面试题48.最长不含重复字符的子字符串

滑动窗口

  • 定义一个 map 数据结构存储 (k, v),其中 key 值为字符,value 值为字符位置

  • 我们定义不重复子串的开始位置为 start,结束位置为 end

  • 随着 end 不断遍历向后,会遇到与 [start, end] 区间内字符相同的情况,此时将字符作为 key 值,获取其 value 值,并更新 start,此时 [start, end] 区间内不存在重复字符

  • 无论是否更新 start,都会更新其 map 数据结构和结果 ans。

class Solution {
    public int lengthOfLongestSubstring(String s) {
		int len = s.length();
		int res = 0;
		Map<Character, Integer> map = new HashMap<>();
		//start表示滑动窗口的左边界,end表示右边界
		for(int start = 0, end = 0; end < len; end++) {
			char c = s.charAt(end); //表示当前待添加的右边界的字符
			//如果这个字符已经在map中,说明窗口中已经有这个字符,此时需重新调整窗口,
			//以保证窗口内没有重复元素
			if(map.containsKey(c)) { 
				//判断已存在字符去掉后的窗口的新左边界(新左边界为这个字符的后一个位置)
				//和之前的左边界哪个大,取较大的那个
				start = Math.max(start, map.get(c) + 1);
			}
			res = Math.max(res, end - start + 1);
			//更新此字符的位置
			map.put(c, end);
		}
		return res;
	}
}
  • 时间复杂度O(n):其中 N 为字符串长度,需遍历计算整个字符串。
  • 空间复杂度 O(1) : 字符的 ASCII 码范围为 0 ~ 127 ,哈希表最多使用 O(128) = O(1) 大小的额外空间。

优化

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(s == null || s.length() < 1) return 0;
        int len = s.length();
        int[] arr = new int[128];
        for(int i = 0; i < 128; i++) arr[i] = -1; //初始化
        int res = 0;
        int left = 0; //左边界
        for(int i = 0; i < len; i++) { //i在这里表示右边界,遍历到哪个字符了
            char c = s.charAt(i);
            left = Math.max(left, arr[c] + 1); //实时更新左边界
            res = Math.max(res, i - left + 1); 
            arr[c] = i; //更新字符的位置
        }
        return res;
    }
}

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

面试题49.丑数

在这里插入图片描述
思路:

丑数的递推性质:丑数只包含因子2,3,5,因此有 丑数 = 某较小丑数 × 某因子

设已知长度为 n 的丑数序列 x1, x2… xn,求第 n+1 个丑数 xn+1。其递推公式

xn+1 = min(xa × 2,xb × 3,xc × 5) 其中 a,b,c ∈ [1,n]

在这里插入图片描述

class Solution {
    public int nthUglyNumber(int n) {
		int[] dp = new int[n];
		dp[0] = 1;
		int a = 0, b = 0, c = 0; //下个应该通过乘2来获得新丑数的数据是第a个, 同理b, c
		for(int i = 1; i < n; i++) {
			int n2 = dp[a] * 2;
			int n3 = dp[b] * 3;
			int n5 = dp[c] * 5;
			dp[i] = Math.min(Math.min(n2, n3), n5);
			//第a个数已经通过乘2得到了一个新的丑数,
			//那下个需要通过乘2得到一个新的丑数的数应该是第(a+1)个数
			if(dp[i] == n2) a++;
			// 第b个数已经通过乘3得到了一个新的丑数,
			//那下个需要通过乘3得到一个新的丑数的数应该是第(b+1)个数
			if(dp[i] == n3) b++;
			// 第c个数已经通过乘5得到了一个新的丑数,
			//那下个需要通过乘5得到一个新的丑数的数应该是第(c+1)个数
			if(dp[i] == n5) c++;
		}
		return dp[n - 1];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值