继续分享面试经常出现的算法题和解法。
第一部分见:字节面试高频百题(一)
第二部分见:字节面试高频百题(二)
第三部分见:字节面试高频百题(三)
61、反转数字
牛客 57 反转数字
https://www.nowcoder.com/practice/1a3de8b83d12437aa05694b90e02f47a?tpId=117
思路:
注意不能引入 long 类型
通过求余倒着计算结果,每加入一个字符要判断下加入这个字符会不会超限
public int reverse (int x) {
int res = 0;
while (x != 0) {
int c = x % 10;
//判断当前字符加入结果集是否超限
if (res > 0 && isOutMax(res, c))return 0;
if (res < 0 && isOutMin(res, c))return 0;
res = res * 10 + c;
//倒数下一位
x /= 10;
}
return res;
}
private boolean isOutMax(int target, int c) {
if (target > Integer.MAX_VALUE / 10) return true;
return target == Integer.MAX_VALUE / 10 && c > Integer.MAX_VALUE % 10;
}
private boolean isOutMin(int target, int c) {
if (target < Integer.MIN_VALUE / 10) return true;
//注意取反比较
return target == Integer.MIN_VALUE / 10 &&
(c - '0') > -(Integer.MIN_VALUE % 10);
}
62、重排链表
牛客 2 重排链表:
https://www.nowcoder.com/practice/3d281dc0b3704347846a110bf561ef6b?tpId=196
思路:
给一个单链表,将链表两两首尾相连
原链表:1->2->3->4.....n->null
对折后链表:1->n->2->n-1->3->n-2.......->null
示例:1 2 3 4 5 6 编程 1 6 2 5 3 4
找到链表中点,反转后链表,遍历前链表插入后链表节点
//找中位节点,反转后表,遍历前表,交替插入后表节点
public void reorderList(ListNode head) {
if (head == null || head.next == null || head.next.next == null) return ;
//快慢指针找中位节点,p1 慢指针,p2快指针
ListNode p1 = head, p2 = head;
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
while (p2 != null && p2.next != null) {
pre = p1;
p1 = p1.next;
p2 = p2.next.next;
}
// 此时p1走到后表的表头,需要断开前表和后表连接
pre.next = null;
//以p1为表头反转后表,返回表头
ListNode head2 = reverse(p1);
//p1指向前表表头,p2指向后表表头,遍历前表,交替插入后表节点
p1 = head;
p2 = head2;
while (p1 != null) {
ListNode head1Next = p1.next;
ListNode head2Next = p2.next;
p1.next = p2;
p2.next = head1Next;
//指针继续前进
p1 = head1Next;
p2 = head2Next;
}
//p2如果还有节点要连上来
if (p2 != null) {
p1 = head;
dummy.next = head;
pre = dummy;
while (p1 != null) {
pre = p1;
p1 = p1.next;
}
pre.next = p2;
}
}
//反转链表返回反转后的表头节点
private ListNode reverse(ListNode head){
if(head == null || head.next == null) return head;
ListNode last = reverse(head.next);
head.next.next = head;
head.next = null;
return last;
}
63、矩阵元素查找
牛客 86 矩阵元素查找:
https://www.nowcoder.com/practice/3afe6fabdb2c46ed98f06cfd9a20f2ce?tpId=117
思路:
行维度倒序剪枝
行维度从后往前,当前元素值比目标值大,意味着这一列都比它大,没必要循环
public int[] findElement (int[][] mat, int n, int m, int x) {
int[] ans = new int[2];
//行维度从后往前,当前元素值比目标值大,意味着这一列都比它大,没必要循环
for (int i = n - 1; i >= 0; i--) {
if (mat[i][0] > x) {
continue;
}
for (int j = 0; j < m; j++) {
if (mat[i][j] == x) {
ans[0] = i;
ans[1] = j;
}
}
}
return ans;
}
64、缺失的第一个正整数
牛客 30 缺失的第一个正整数
https://www.nowcoder.com/practice/50ec6a5b0e4e45348544348278cdcee5?tpId=196
思路:
排序,遍历时非正正数跳过,正整数和递增的目标数比对,一致目标数递增,不一致返回当前目标数
public int minNumberDisappeared (int[] nums) {
if(nums == null || nums.length == 0) return 1;
Arrays.sort(nums);
int n = 1;
for(int i = 0; i < nums.length;i++){
if(nums[i] <= 0) continue;
//出现了正整数
if(nums[i] == n) {
n++;
continue;
}else {
return n;
}
}
return n;
}
65、丢失的数字
力扣 268 丢失的数字:
https://leetcode.cn/problems/missing-number/description/
思路:
排序+遍历
进阶:排序+二分
public int missingNumber(int[] nums) {
Arrays.sort(nums);
int n = 0;
for (int i = 0; i < nums.length ; i++) {
if (nums[i] == n) {
n++;
continue;
} else {
return n;
}
}
return n;
}
66、链表的奇偶重排
力扣 328/牛客 133 链表的奇偶重排:
https://www.nowcoder.com/practice/02bf49ea45cd486daa031614f9bd6fc3?tpId=196
https://leetcode.cn/problems/odd-even-linked-list/description/
思路:
合并链表的思路,分别把奇数节点和偶数节点合并到新链表中
public ListNode oddEvenList (ListNode head) {
if (head == null || head.next == null) return head;
ListNode dummy = new ListNode(-1);
//p1 奇数位,p2偶数位
ListNode p = dummy, p1 = head, p2 = head.next;
//奇数位取每两步的值
while (p1 != null && p1.next != null) {
p.next = new ListNode(p1.val);
p1 = p1.next.next;
p = p.next;
}
//p1还没被连接
if (p1 != null) {
p.next = new ListNode(p1.val);
p = p.next;
}
//偶数位取每两步的值
while (p2 != null && p2.next != null) {
p.next = new ListNode(p2.val);
p2 = p2.next.next;
p = p.next;
}
//p2还没被连接
if (p2 != null) {
p.next = p2;
}
return dummy.next;
}
67、二叉树中的最大路径和
力扣 124/牛客 6 二叉树中的最大路径和:
https://www.nowcoder.com/practice/da785ea0f64b442488c125b441a4ba4a?tpId=196
https://leetcode.cn/problems/binary-tree-maximum-path-sum/description/
思路:
左右子树贡献求最大,注意负数情况的处理,函数返回节点贡献值选左子树或者由子树
private int res = Integer.MIN_VALUE;
public int maxPathSum (TreeNode root) {
if(root == null) return 0;
traverse(root);
return res;
}
//遍历二叉树,返回以root为根节点的最大路劲和
private int traverse(TreeNode root){
if(root == null) return 0;
//有负数情况,要取大于0的
int leftMax = traverse(root.left);
leftMax = Math.max(leftMax,0);
int rightMax = traverse(root.right);
rightMax = Math.max(rightMax,0);
//已有最大值结果和当前路径和比较
int curMax = leftMax + rightMax + root.val;
res = Math.max(res,curMax);
//返回节点的最大贡献值(此节点要么采用左子树做路径,要么采用右子树做路径)
return root.val + Math.max(leftMax,rightMax);
}
68、对称二叉树
力扣 101/牛客 16 对称的二叉树:
https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=196
https://leetcode.cn/problems/symmetric-tree/description/
思路:
二叉树后续遍历:
节点比较
层序遍历借助栈
public boolean isSymmetrical (TreeNode pRoot) {
return traverse(pRoot, pRoot);
}
private boolean traverse(TreeNode root1, TreeNode root2) {
if (root1 == null && root2 == null) return true;
if (root1 == null || root2 == null) return false;
//当前层结果
boolean curRes = root1.val == root2.val;
//root1 左节点和 root2 右节点比较
boolean leftRight = traverse(root1.left, root2.right);
//root1 右节点和 root2 左节点比较
boolean rightLeft = traverse(root1.right, root2.left);
return curRes && leftRight && rightLeft;
}
69、括号生成
牛客 26 括号生成
https://www.nowcoder.com/practice/c9addb265cdf4cdd92c092c655d164ca?tpId=196
思路:
递归树,左子树处理左括号,右子树处理有括号
left 为当前左括号个数,right 为当前右括号个数,n 对括号。递归树终止条件是 left==n&&left==right
ArrayList<String> res = new ArrayList<>();
public ArrayList<String> generateParenthesis (int n) {
traverse(n, 0, 0, "");
return res;
}
//left 为当前左括号个数,right 为当前右括号个数,n 对括号。递归树终止条件是 left==n&&left==right
private void traverse(int n, int left, int right, String cur) {
//左括号比n大或者右括号比左括号大,非法
if (left > n || right > left) return;
if (left == n && left == right) {
res.add(cur);
return;
}
//追加左括号
traverse(n, left + 1, right, cur + "(");
//追加右括号
traverse(n, left, right + 1, cur + ")");
}
70、顺时针旋转矩阵
牛客 18 顺时针旋转矩阵
https://www.nowcoder.com/practice/2e95333fbdd4451395066957e24909cc?tpId=196
思路:
对角线置换+水平前后置换
//对角线置换+水平前后置换
public int[][] rotateMatrix (int[][] mat, int n) {
//矩阵转置
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
//交换上三角与下三角对应的元素
int temp = mat[i][j];
mat[i][j] = mat[j][i];
mat[j][i] = temp;
}
}
//每行翻转
for (int i = 0; i < n; i++) {
for (int j = 0; j < n / 2; j++) {
int temp = mat[i][j];
mat[i][j] = mat[i][n - j - 1];
mat[i][n - j - 1] = temp;
}
}
return mat;
}
71、删除有序链表中重复的元素-I
力扣 83/牛客 25 删除有序链表中重复的元素-I
https://leetcode.cn/problems/remove-duplicates-from-sorted-list/
https://www.nowcoder.com/practice/c087914fae584da886a0091e877f2c79?tpId=196
思路:
快慢指针。都初始化 head,遍历快指针,当快慢指针值不等时,慢指针直接连接不等值的快指针,此时慢指针再推进,达到跳过重复数字的效果。
注意循环结束要对慢指针跳过后面重复元素的连接: 2-3-3 的case。
public ListNode deleteDuplicates(ListNode head) {
if(head == null) return null;
ListNode slow = head,fast = head;
while(fast != null){
if(slow.val != fast.val){
//对应数组快慢指针操作中的 nums[slow] = nums[fast]
slow.next = fast;
//对应数组快慢指针操作中的 slow++
slow = slow.next;
}
fast = fast.next;
}
// 断开与后面重复元素的连接
slow.next = null;
return head;
}
72、加起来和为目标值的组合
牛客 238 加起来和为目标值的组合:
https://www.nowcoder.com/practice/172e6420abf84c11840ed6b36a48f8cd?tpId=196
思路:
回溯法
public ArrayList<ArrayList<Integer>> combinationCount (int target, int[] nums) {
if (nums.length == 0) return ans;
Arrays.sort(nums);//排序后便于剪枝
backtrack(target, nums, 0, 0);
return ans;
}
public void backtrack(int target, int[] nums, int sum, int start) {
//满足条件追加结果
if (target == sum) {
ans.add(new ArrayList<>(path));
return ;
}
//for 选择 in 选择列表
for (int i = start; i < nums.length; i++) {
//剪枝,结果和已经大于目标值,无需下一轮递归
if (sum + nums[i] > target) break;
//做选择
path.addLast(nums[i]);
sum = sum + nums[i];
//递归调用
backtrack(target, nums, sum, i);
//撤销选择
sum = sum - nums[i];
path.removeLast();
}
}
73、左边界二分查找
牛客 105 二分查找-II:
https://www.nowcoder.com/practice/4f470d1d3b734f8aaf2afb014185b395
思路:
等值时收缩右边界
public int search (int[] nums, int target) {
int left = 0, right = nums.length - 1;
int res = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
//搜索边界变为 [mid+1,right]
left = mid + 1;
} else if (nums[mid] > target) {
//搜索边界变为 [left,mid-1]
right = mid -1;
}else if(nums[mid] == target){
res = mid;
//收缩右边界
right = mid -1;
}
}
return res;
}
74、序列化二叉树
力扣 297/牛客 123 序列化二叉树
https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/description/
https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=196&tqId=37116
思路:
在相应位置做逻辑处理
【前序】
private String SEP = ",";
private String NULL = "#";
//先序序列化
String Serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
traverse(root, sb);
return sb.toString();
}
void traverse(TreeNode root, StringBuilder sb) {
if (root == null) {
sb.append(NULL).append(SEP);
return;
}
sb.append(root.val).append(SEP);
traverse(root.left, sb);
traverse(root.right, sb);
}
TreeNode Deserialize(String str) {
LinkedList<String> nodes = new LinkedList<>();
for(String s : str.split(SEP)){
nodes.add(s);
}
return Deserialize(nodes);
}
TreeNode Deserialize(LinkedList<String> nodes){
if(nodes.isEmpty()) return null;
String rootVal = nodes.removeFirst();
if(rootVal.equals(NULL)) return null;
TreeNode root = new TreeNode(Integer.parseInt(rootVal));
root.left = Deserialize(nodes);
root.right = Deserialize(nodes);
return root;
}
【层序】
String SEP = ",";
String NULL = "#";
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if (root == null)
return "";
StringBuilder sb = new StringBuilder();
// 初始化队列,将 root 加入队列
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
TreeNode cur = q.poll();
// 层序遍历代码位置
if (cur == null) {
sb.append(NULL).append(SEP);
continue;
}
sb.append(cur.val).append(SEP);
q.offer(cur.left);
q.offer(cur.right);
}
return sb.toString();
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if (data.isEmpty())
return null;
String[] nodes = data.split(SEP);
// 第一个元素就是 root 的值
TreeNode root = new TreeNode(Integer.parseInt(nodes[0]));
// 队列q 记录父节点,将root加入队列
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
for (int i = 1; i < nodes.length;) {
// 队列中存的都是父节点
TreeNode parent = q.poll();
// 父节点对应的左侧子节点的值
String left = nodes[i++];
if (!left.equals(NULL)) {
parent.left = new TreeNode(Integer.parseInt(left));
q.offer(parent.left);
} else {
parent.left = null;
}
// 父节点对应的右侧子节点的值
String right = nodes[i++];
if (!right.equals(NULL)) {
parent.right = new TreeNode(Integer.parseInt(right));
q.offer(parent.right);
} else {
parent.right = null;
}
}
return root;
}
75、字符串变形
牛客 89 字符串变形:
https://www.nowcoder.com/practice/c3120c1c1bc44ad986259c0cf0f0b80e?tpId=196
思路:
空格拆子字符串,针对子字符串做大小写转换
public String trans (String s, int n) {
String[] arr = s.split(" ", -1);
StringBuilder res = new StringBuilder();
for (int i = arr.length - 1; i >= 0 ; i--) {
res.append(upperLowerReverse(arr[i]));
if (i == 0) break;
res.append(" ");
}
return res.toString();
}
//字符串中大小写字母反转
private String upperLowerReverse(String str) {
char[] strArr = str.toCharArray();
for (int i = 0; i < strArr.length; i++) {
if (Character.isUpperCase(strArr[i])) {
strArr[i] = Character.toLowerCase(strArr[i]);
} else {
strArr[i] = Character.toUpperCase(strArr[i]);
}
}
return new String(strArr);
}
76、环形链表的约瑟夫问题
牛客 132 环形链表的约瑟夫问题:
https://www.nowcoder.com/practice/41c399fdb6004b31a6cbb047c641ed8a?tpId=196
思路:
构建一个环形链表,指针符合要求则删除节点
public int ysf (int n, int m) {
//构建一个环形链表,指针符合要求则删除节点
ListNode head = buildCycleList(n);
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
ListNode p = head;
int cnt = 0;
while(p.next != p){
cnt++;
if(cnt == m){
//删除当前节点
pre.next = p.next;
//清空报名
cnt = 0;
}
//更新前驱节点和移动指针
pre = p;
p = p.next;
}
return p.val;
}
public ListNode buildCycleList(int n){
ListNode head = new ListNode(1);
ListNode p = head;
for(int i = 2;i <=n;i++){
p.next = new ListNode(i);
p = p.next;
}
//成环
p.next = head;
return head;
}
class ListNode{
public int val;
public ListNode next;
public ListNode(int val){
this.val = val;
}
}
77、最大数
牛客111 最大数:
https://www.nowcoder.com/practice/fc897457408f4bbe9d3f87588f497729?tpId=196
思路:
整型转字符串,排序并拼接
public String solve (int[] nums) {
ArrayList<String> list = new ArrayList<>();
//将整型的数字转化为字符串
for (int i = 0; i < nums.length; i ++) {
list.add(String.valueOf(nums[i]));
}
//排序:字典降序
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return (b + a).compareTo(a + b);
}
});
//这个地方需要注意如果第一个字符串已经是0了,那么直接输出0
if (list.get(0).equals("0")) return "0";
//结果字符串
StringBuilder res = new StringBuilder();
//将排序好后的字符串一次相加就是最终的结果
for (int i = 0; i < list.size();i ++) {
res.append(list.get(i));
}
return res.toString();
}
78、验证 IP 地址
牛客 113 验证 IP 地址:
https://www.nowcoder.com/practice/55fb3c68d08d46119f76ae2df7566880?tpId=196
思路:
根据 IP 特点做针对性检查
boolean isIPv4 (String IP) {
if (IP.indexOf('.') == -1) {
return false;
}
String[] s = IP.split("\\.");
//IPv4必定为4组
if (s.length != 4)
return false;
for (int i = 0; i < s.length; i++) {
//不可缺省,有一个分割为零,说明两个点相连
if (s[i].length() == 0)
return false;
//比较数字位数及不为零时不能有前缀零
if (s[i].length() < 0 || s[i].length() > 3 || (s[i].charAt(0) == '0' &&
s[i].length() != 1))
return false;
int num = 0;
//遍历每个分割字符串,必须为数字
for (int j = 0; j < s[i].length(); j++) {
char c = s[i].charAt(j);
if (c < '0' || c > '9')
return false;
//转化为数字比较,0-255之间
num = num * 10 + (int)(c - '0');
if (num < 0 || num > 255)
return false;
}
}
return true;
}
boolean isIPv6 (String IP) {
if (IP.indexOf(':') == -1) {
return false;
}
String[] s = IP.split(":", -1);
//IPv6必定为8组
if (s.length != 8) {
return false;
}
for (int i = 0; i < s.length; i++) {
//每个分割不能缺省,不能超过4位
if (s[i].length() == 0 || s[i].length() > 4) {
return false;
}
for (int j = 0; j < s[i].length(); j++) {
//不能出现a-fA-F以外的大小写字符
char c = s[i].charAt(j);
boolean expr = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' &&
c <= 'F') ;
if (!expr) {
return false;
}
}
}
return true;
}
public String solve (String IP) {
if (isIPv4(IP))
return "IPv4";
else if (isIPv6(IP))
return "IPv6";
return "Neither";
}
79、兑换零钱
力扣 322/牛客 126 兑换零钱(一):
https://www.nowcoder.com/practice/3911a20b3f8743058214ceaa099eeb45?tpId=196
https://leetcode.cn/problems/coin-change/description/
思路:
动态规划 子问题
int[] memo;
public int coinChange(int[] coins, int amount) {
memo = new int[amount + 1];
Arrays.fill(memo, -66);
return dp(coins, amount);
}
// 返回金额为amount需要的最小硬币数
int dp(int[] coins, int amount) {
if (amount == 0)
return 0;
if (amount < 0)
return -1;
if (memo[amount] != -66)
return memo[amount];
int res = Integer.MAX_VALUE;
for (int coin : coins) {
int subProNum = dp(coins, amount - coin);
if (subProNum == -1)
continue;
res = Math.min(res, subProNum + 1);
}
memo[amount] = res == Integer.MAX_VALUE ? -1 : res;
return memo[amount];
}
80、数组中的逆序对
牛客 118 数组中的逆序对:
https://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=196
思路:
归并排序过程中计算数量
int count = 0;
public int InversePairs(int [] array) {
// 长度小于2则无逆序对
if (array.length < 2) return 0;
// 进入归并
mergeSort(array, 0, array.length - 1);
return count;
}
public void mergeSort(int[] array, int left, int right) {
// 找分割点
int mid = left + (right - left) / 2;
if (left < right) {
// 左子数组
mergeSort(array, left, mid);
// 右子数组
mergeSort(array, mid + 1, right);
// 并
merge(array, left, mid, right);
}
}
public void merge(int[] array, int left, int mid, int right) {
// 创建临时数组,长度为此时两个子数组加起来的长度
int[] arr = new int[right - left + 1];
// 临时数组的下标起点
int c = 0;
// 保存在原数组的起点下标值
int s = left;
// 左子数组的起始指针
int l = left;
// 右子数组的起始指针
int r = mid + 1;
while (l <= mid && r <= right ) {
// 当左子数组的当前元素小的时候,跳过,无逆序对
if (array[l] <= array[r]) {
// 放入临时数组
arr[c] = array[l];
// 临时数组下标+1
c++;
// 左子数组指针右移
l++;
} else { // 否则,此时存在逆序对
// 放入临时数组
arr[c] = array[r];
// 逆序对的个数为 左子数组的终点- 当前左子数组的当前指针
count += mid + 1 - l;
count %= 1000000007;
// 临时数组+1
c++;
// 右子数组的指针右移
r++;
}
}
// 左子数组还有元素时,全部放入临时数组
while (l <= mid)
arr[c++] = array[l++];
// 右子数组还有元素时,全部放入临时数组
while (r <= right)
arr[c++] = array[r++];
// 将临时数组中的元素放入到原数组的指定位置
for (int num : arr) {
array[s++] = num;
}
}
我是蜗牛,大厂程序员,专注技术原创和个人成长,正在互联网上摸爬滚打。欢迎关注我,和蜗牛一起成长,我们一起牛~下期见!
推荐阅读:
点阅读原文发现更多Java资源宝藏~
点分享
点收藏
点点赞
点在看