面试题16、17、18、19

面试题16.数值的整数次方

在这里插入图片描述

二分法推导:xn = xn/2 × xn/2 = (x2)n/2 ,令 n/2 为整数,则需要分为奇偶两种情况:

  • 当 n 为偶数:xn = (x2)n/2
  • 当 n 为奇数:xn = x(x2)n/2 ,即会多出一项 x;

幂结果获取:

  • 根据推导,可通过循环 x = x2 操作,每次把幂从 n 降至 n/2,直至将幂降为 0;
  • 设 res = 1,则初始状态 xn = xn × res。在循环二分时,每当 n 为奇数时,将多出的一项 x 乘入 res,则最终可化至 xn = x0 × res = res,返回 res 即可。

在这里插入图片描述

class Solution {
    public double myPow(double x, int n) {
        if(n == 0) return 1;
        if(n == 1 || x == 0) return x;
        //当 n = -2147483648 时执行 n = -n 会因越界而赋值出错。
        //解决方法是先将 n 存入 long 变量 b ,后面用 b 操作即可。
        long b = n; 
        if(b < 0) {
            x = 1 / x;
            b = -b;
        }
        double res = 1;
        while(b > 0) {
        	//若b是奇数,由于接下来还要减半,需要将多余的那一个x累乘积到res中;
            if(b % 2 == 1) res *= x;
            x *= x;
            b /= 2;
        }
        return res;
    }
}

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

面试题17. 打印从1到最大的n为数

在这里插入图片描述
1、大数越界问题: 当 n 较大时,end 会超出 int32 整型的取值范围,超出取值范围的数字无法正常存储。因此,大数的表示应用字符串 String 类型。

2、经观察可知,生成的数字实际上是 n 位 0 - 9 的全排列,因此可避开进位操作,通过递归生成数字的 String 列表。

3、基于分治算法的思想,先固定高位,向低位递归,当个位已被固定时,添加数字的字符串。例如当 n = 2 时(数字范围 1 - 99 ),固定十位为 0 - 9 ,按顺序依次开启递归,固定个位 0 - 9 ,终止递归并添加数字字符串。

在这里插入图片描述

自己的做法

class Solution {
    public int[] printNumbers(int n) {
        StringBuilder res = new StringBuilder();
        StringBuilder sb = new StringBuilder();
        dfs(res, sb, 0, n);
        res.deleteCharAt(res.length() - 1);
        return res.toString();
    }
    public void dfs(StringBuilder res, StringBuilder sb, int index, int n) {
        if(index == n) {
            if(sb.length() > 0) {
                res.append(sb.toString());
                res.append(",");
            }
            return;
        }
        for(int i = 0; i <= 9; i++) {
            char ch = (char) ('0' + i);
            if(!(ch == '0' && sb.length() == 0)) sb.append(ch);
            dfs(res, sb, index + 1, n);
            if(sb.length() > 0) sb.deleteCharAt(sb.length() - 1);
        }
    }
}

全排列初步代码:

class Solution {
	StringBuilder res;
	int count = 0, n;
	char[] num;
	char[] loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
	public String printNumbers(int n) {
		this.n = n;
		res = new StringBuilder(); //数字字符串集,如"0,1,2,3,4,5,6,7,8,9"
		num = new char[n]; //定义长度为n的字符列表
		dfs(0); //开始全排列递归,从0开始
		res.deleteCharAt(res.length() - 1); //删除最后多余的逗号
		return res.toString(); //转化为字符串并返回
	}
	public void dfs(int x) {
		if(x == n) { //终止条件:已固定完所有位
			res.addend(String.valueOf(num) + ","); //拼接num并添加至res尾部,使用逗号隔开
			return;
		}
		for(char i : loop) { //遍历 ‘0’-‘9’
			num[x] = i; //固定第 x 位为 i
			dfs(x + 1); //开启固定第 x+1 位
		}
	}
}

返回的数字集字符串如下所示:
在这里插入图片描述
可知存在问题:

  • 1.诸如00,01,02,…应显示为0,1,2…,即应删除高位多余0
  • 2.此方法从0开始生成,题目要求从1开始

解决方法:

1、删除高位多余的0

  • 字符串左边界定义:声明变量 start 规定字符串的左边界,表示比当前字符串最高位的数字还高位的数字有几位,以保证添加的数字字符串 num[start:] 中无高位多余的0,。例如当 n = 2 时,1 - 9 时 start = 1,10 - 99时 start = 0.

  • 左边界 start 变化规律:当输出数字的所有位都是 9 时,则下个数字需要向更高位进 1,此时左边界 start 需要减1(即高位多余的0减少一个)。例如当 n = 3 (数字范围 1 - 999 )时,左边界 start 需要减 1 的情况有: “009” 进位至 “010” , “099” 进位至 “100” 。设数字各位中 9 的数量为 nine,所有位都为9的判断条件可用以下公式表示: n - start = nine

  • 统计 nine 的方法:固定第 x 位时,当 i = 9 则执行 nine = nine + 1,并在回溯前恢复 nine = nine - 1。

2、列表从 1 开始

  • 在以上方法的基础上,添加数字字符串前判断其是否为 “0”,若为 “0” 则直接跳过。

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

class Solution {
	StringBuilder res;
	int nine = 0; //各位中9的数量
	int start;
	char[] num; //长度为n的字符列表
	char[] loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
	public String printNumbers(int n) {
		res = new StringBuilder();
		num = new char[n];
		start = n - 1;
		dfs(0, n);
		res.deleteCharAt(res.length() - 1); //删除最后多余的逗号
		return res.toString();
	}
	public void dfs(int x, int n) {
		if(x == n) {
			//从索引为 start 处开始截取字符串,这样就避免了截取到高位的0或者其他数字
			String s = String.valueOf(num).substring(start);
			if(!s.equals("0")) res.append(s + ",");
			//判断所有位是否都为9,若为9,则表示此时为9,99,999...,接下来需进一位
			//则高位为0的左边界就应少一位,所以 start--
			if(n - start == nine) start--;
			return;
		}
		for(char i : loop) {
			if(i == '9') nine++;
			num[x] = i; //固定第 x 位为 i
			dfs(x + 1);
		}
		nine--;
	}
}

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

面试题18.删除链表的节点

在这里插入图片描述

class Solution {
    public ListNode deleteNode(ListNode head, int val) {
		if(head == null) return head;
		if(head.val == val) return head.next; //如果头节点是要删除节点
		ListNode cur = head; 
		//此时判断的是cur的下一个节点是否为要删除节点
		//如果cur.next为空,说明链表中没有要删除节点
		while(cur.next != null && cur.next.val != val) cur = cur.next;
		//能进入这个循环的条件就是链表中存在要删除的节点,为当前节点的下一节点
		if(cur.next != null) cur.next = cur.next.next;
		return head;
    }
}
  • 时间复杂度O(N) : N为链表长度,删除操作平均需要循环 N/2 次,最差 N 次。

题目二:删除链表中的重复节点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

//public class ListNode {
//    int val;
//    ListNode next = null;
//    ListNode(int val) {
//        this.val = val;
//   }
//}
public class deleteList {
    public ListNode printListFromTailToHead(ListNode head) {
    	if(head == null || head.next == null) return head;
		ListNode newHead = new ListNode(-1); //设置哑节点,处理头节点也是重复节点的情况
		newHead.next = head;
		ListNode node = newHead;
		while(node.next != null) {
			//如果当前节点不等于下一节点,则下移一个节点
			if(node.val != node.next.val) node = node.next; 
			//如果两者相等,令当前节点指向下一节点的下一节点。
			//此时还是停留在当前节点,继续判断当前节点是否等于下一节点
			else {
				node.next = node.next.next;
			}
		}
		return newHead.next;
    }
}

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

面试题19.正则表达式匹配

在这里插入图片描述
在这里插入图片描述
解题思路

假设主串为 A,模式串为 B 从最后一步出发,需要关注最后进来的字符。假设 A 的长度为 n ,B 的长度为 m ,关注正则表达式 B 的最后一个字符是谁,它有三种可能,正常字符、∗ 和 .(点),那针对这三种情况讨论即可,如下:

1、如果 B 的最后一个字符是正常字符,那就是看 A[n−1] 是否等于 B[m−1],相等则看 A{0…n-2}与 B{0…m-2},不等则是不能匹配,这就是子问题。

2、如果 B 的最后一个字符是 (.),它能匹配任意字符,直接看 A{0…n-2} 与 B{0…m-2}

3、如果 B 的最后一个字符是 ∗ 它代表 B[m-2]=c 可以重复0次或多次,它们是一个整体 c∗

  • 情况一:A[n−1] 是 0 个 c,B 最后两个字符废了(比如 A = a,B = ab*,两者是匹配的),能否匹配取决于 A{0…n-1} 和 B{0…m-3} 是否匹配
  • 情况二:A[n-1] 是多个 c 中的最后一个(这种情况必须 A[n-1]=c 或者 c= .),所以 A 匹配完往前挪一个,B 继续匹配,因为可以匹配多个,继续看 A{0…n-2} 和 B{0…m-1} 是否匹配。

1、状态定义:dp[i][j] 代表字符串 A 的前 i 个字符和 B 的前 j 个字符能否匹配

2、转移方程

  • 对于前面两个情况,可以合并成一种情况 dp[i][j] = dp[i-1][j-1]
  • 对于第三种情况,对于 c∗ 分为看和不看两种情况
    • 不看:直接砍掉正则串的后面两个, dp[i][j] = dp[i][j-2]
    • 看:正则串不动,主串前移一个,dp[i][j] = dp[i-1][j]

3、初始条件

特判:需要考虑空串空正则

  • 空串和空正则是匹配的, dp[0][0] = true
  • 空串和非空正则,不能直接定义 true 和false,必须要计算出来。(比如A= ‘’ ‘’ ,B=a∗b∗c∗)
  • 非空串和空正则必不匹配,f[1][0]=…=f[n][0]=false
  • 非空串和非空正则,那肯定是需要计算的了。

大体上可以分为空正则和非空正则两种,空正则也是比较好处理的,对非空正则我们肯定需要计算,非空正则的三种情况,前面两种可以合并到一起讨论,第三种情况是单独一种,那么也就是分为当前位置是 ∗ 和不是 ∗ 两种情况了。

class Solution {
    public boolean isMatch(String A, String B) {
		int n = A.length();
		int m = B.length();
		boolean[][] dp = new boolean[n + 1][m + 1];
		
		for(int i = 0; i <= n; i++) {
			for(int j = 0; j <= m; j++) {
				//分为空正则和非空正则两种
				//空正则
				if(j == 0) {
					//空串和空正则是匹配的,f[0][0] = truef[0][0]=true
					//非空串和空正则必不匹配,f[1][0]=...=f[n][0]=false
					dp[i][j] = i == 0; 
				} else { //非空正则
					//分为两种情况 * 和 非*
					if(B.charAt(j - 1) != '*') {
						if(i > 0 && (A.charAt(i - 1) == B.charAt(j - 1) || B.charAt(j - 1) == '.')) {
							dp[i][j] = dp[i - 1][j - 1];
						}
					} else {
						//碰到 * 了,分为看和不看两种情况
						//不看
						if(j >= 2) {
							dp[i][j] |= dp[i][j - 2];
						}
						//看
						if(i >= 1 && j >= 2 && (A.charAt(i - 1) == B.charAt(j - 2) || B.charAt(j - 2) == '.')) {
							dp[i][j] |= dp[i - 1][j];
						}
						//其中,第一步先算的是不看‘*’的情况,然后第二步再算看‘*’的情况
						//也就是说,对于f[i][j]我们会算两次。如果在第一次,即不看'*'的时候,就已经算出来TURE了
						//那在第二步看'*'的时候。不管结果是ture还是false,都保持true不变
						//因为只要其中有一种情况能完整匹配,结果就为true
					}
				}
			}
		}
		return dp[n][m];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值