第二天
声明:本系列文章仅适合二刷有经验的算法er学习,以后会出详细的每一题的讲解,这里只是简单的说明思路来帮助大家快速刷完Top101,另外博主的算法全程跟着 labuladong 大佬学习,这里特此声明
11.链表相加
解题思路
- 首先将两个链表反转,这样符合加法的规范,即从个位开始相加
- 加法做完之后得到的结果是一个逆序的数字,需要再进行反转将其变为正序
代码实现
public class Solution {
/**
*
* @param head1 ListNode类
* @param head2 ListNode类
* @return ListNode类
*/
public ListNode addInList (ListNode head1, ListNode head2) {
// 进行判空处理
if(head1 == null)
return head2;
if(head2 == null){
return head1;
}
// 反转h1链表
head1 = reverse(head1);
// 反转h2链表
head2 = reverse(head2);
// 创建新的链表头节点
ListNode head = new ListNode(-1);
ListNode nHead = head;
// 记录进位的数值
int tmp = 0;
while(head1 != null || head2 != null){
// val用来累加此时的数值(加数+加数+上一位的进位=当前总的数值)
int val = tmp;
// 当节点不为空的时候,则需要加上当前节点的值
if (head1 != null) {
val += head1.val;
head1 = head1.next;
}
// 当节点不为空的时候,则需要加上当前节点的值
if (head2 != null) {
val += head2.val;
head2 = head2.next;
}
// 求出进位
tmp = val/10;
// 进位后剩下的数值即为当前节点的数值
nHead.next = new ListNode(val%10);
// 下一个节点
nHead = nHead.next;
}
// 最后当两条链表都加完的时候,进位不为0的时候,则需要再加上这个进位
if(tmp > 0){
nHead.next = new ListNode(tmp);
}
// 重新反转回来返回
return reverse(head.next);
}
// 反转链表
ListNode reverse(ListNode head){
if(head == null)
return head;
ListNode cur = head;
ListNode node = null;
while(cur != null){
ListNode tail = cur.next;
cur.next = node;
node = cur;
cur = tail;
}
return node;
}
}
12.单链表的排序
解题思路
把链表每个节点的值都取出来存到一个数组之中,然后对数组进行排序,之后按照顺序把值分别赋给原链表即可
代码实现
public class Solution {
/**
*
* @param head ListNode类 the head node
* @return ListNode类
*/
public ListNode sortInList(ListNode head) {
// write code here
ArrayList<Integer> arrayList = new ArrayList<>();
ListNode p = head;
while (p != null) {
arrayList.add(p.val);
p = p.next;
}
p = head;
Collections.sort(arrayList);
for (int i = 0; i < arrayList.size(); i++) {
p.val = arrayList.get(i);
p = p.next;
}
return head;
}
}
13.判断链表是否是回文结构
解题思路
- 使用双指针先找到链表的中点(如果链表长度是奇数,则 slow 指针还需要在前进一位)
- 反转以 slow 开头的链表
- 使用两个新指针分别指向链表头部和尾部,当尾部指针不为 null 时每次对比两个指针指向的节点值是否相同,不同则立刻退出,直到尾部指针为 null 时返回 true
代码实现
public class Solution {
/**
*
* @param head ListNode类 the head
* @return bool布尔型
*/
public boolean isPail(ListNode head) {
// write code here
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//fast不为null说明长度为奇数,fast需要再往后走一位
if (fast != null) {
fast = fast.next;
}
ListNode left = head;
ListNode right = reverse(slow);
while (right != null) {
if (left.val != right.val) {
return false;
}
left = left.next;
right = right.next;
}
return true;
}
public 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;
}
}
14.链表的奇偶重排
解题思路
- 用双指针 odd 和 even 分别遍历奇数节点和偶数节点,并给偶数节点一个头结点
- 每轮循环使用 even 来判断其后两个元素是否为 null
- 将偶数节点连接在奇数节点后
代码实现
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode oddEvenList(ListNode head) {
// write code here
if (head == null) {
return head;
}
ListNode odd = head, even = head.next, evenHead = even;
while (even != null && even.next != null) {
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
odd.next = evenHead;
return head;
}
}
15.删除有序链表的重复元素-1
解题思路
双指针,如果快指针的值不等于慢指针的值,让慢指针的下一个节点的值被快指针指向节点的值覆盖,否则快指针 +1就行,最后记得吧慢指针后面的节点断开
代码实现
public class Solution {
/**
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode deleteDuplicates(ListNode head) {
// write code here
if (head == null) {
return null;
}
ListNode slow = head, fast = head;
while (fast != null) {
if (slow.val != fast.val) {
slow.next = fast;
slow = slow.next;
}
fast = fast.next;
}
slow.next = null;
return head;
}
}
16.删除有序链表的重复元素-2
解题思路
- 设置虚拟头结点 dummy 防止第一个节点被删除报错的问题发生
- 用一个指针遍历链表,如果相邻两个位置的节点的值是相等的,记录下这个值 temp
- 进入最里层的 while 循环判断当前节点的下一个节点值是否等于 temp ,如果等于就再前进一步,直到不等于
- 返回虚拟头结点的下一个节点即可
代码实现
public class Solution {
/**
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode deleteDuplicates(ListNode head) {
// write code here
if (head == null) {
return null;
}
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode p = dummy;
while (p.next != null && p.next.next != null) {
if (p.next.val == p.next.next.val) {
int temp = p.next.val;
while (p.next != null && p.next.val == temp) {
p.next = p.next.next;
}
} else {
p = p.next;
}
}
return dummy.next;
}
}
18.二维数组中的查找
解题思路
板子题,建议记一下
代码实现
public class Solution {
public boolean Find(int target, int[][] array) {
if (array.length == 0) {
return false;
}
int n = array.length;
if (array[0].length == 0) {
return false;
}
int m = array[0].length;
for (int i = n - 1, j = 0; i >= 0 && j < m; ) {
if (array[i][j] > target) {
i--;
} else if (array[i][j] < target) {
j++;
} else {
return true;
}
}
return false;
}
}
19.寻找峰值
解题思路
- 二分查找首先从数组首尾开始,每次取中间值,直到首尾相遇
- 如果中间值大于等于其右边的元素,说明往右是向下,不会遇到波峰,应该往左
- 如果中间值大于右边的元素,此时往右是向上的,一定会遇到波峰
代码实现
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @return int整型
*/
public int findPeakElement(int[] nums) {
// write code here
if (nums == null || nums.length == 0) {
return -1;
}
int n = nums.length;
int lo = 0, hi = n - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (nums[mid] > nums[mid + 1]) {
hi = mid;
} else {
lo = mid + 1;
}
}
return hi;
}
}
20.数组中的逆序对
解题思路
求逆序对就是统计有多少个前大后小的数对,问题和归并两个有序数组求前大后小数对一样。所以现在的问题变为统计子问题中逆序对的个数。
代码实现
public class Solution {
int[] nums,temp;
public int merge_sort(int l,int r){
if(l>=r)return 0;
int m, i, j, k;
long res;
m = (l+r)/2;
res = (merge_sort(l,m)+merge_sort(m+1,r))%1000000007;
//merge
for(k=l;k<=r;k++)temp[k]=nums[k];
i = l;j = m+1;k = l;
for(k=l;k<=r;k++){
if(i==m+1)nums[k]=temp[j++];
else if(j==r+1||temp[i]<=temp[j])nums[k] = temp[i++];
else {
res=(res + m-i+1)%1000000007;
nums[k]=temp[j++];
}
}
return (int)res;
}
public int InversePairs(int [] array) {
nums = array;
temp = new int[array.length];
return merge_sort(0,array.length-1);
}
}
21.旋转数组的最小数字
解题思路
反正就是打乱一个有序数组,直接遍历求最值也行,不过考虑到时间复杂度,还是需要用到二分的思想
- 双指针指向旋转后数组的首位,作为区间的端点
- 若区间中点值大于右界值,则最小的数字一定在中点右边
- 若是区间中点值等于区间右界值,则应逐个缩减右界
- 若是区间中点值小于右界值,则最小的数字一定在中点左边
代码实现
public class Solution{
public int minNumberInRotateArray(int[] array){
int left = 0;
int right = array.length-1;
if(array[mid] > array[right]){
left = mid + 1;
}else if(array[mid] == array[right]){
right--;
}else{
right = mid;
}
return array[left];
}
}
22.比较版本号
解题思路
- 使用 split 函数按照“ . ”将两个原始的字符串分割,使每个修订号的数字单独呈现在数组中
- 遍历数组,每次各自取出一个数字比较,较短的版本号没有可取的数字就直接取0
- 遍历取出的数字字符串,将其转换成数字,然后比较数字大小,根据大小关系返回1或者-1,如果全部比较完都无法比较出大小,则返回0
代码实现
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 比较版本号
* @param version1 string字符串
* @param version2 string字符串
* @return int整型
*/
public int compare(String version1, String version2) {
// write code here
String[] nums1 = version1.split("\\.");
String[] nums2 = version2.split("\\.");
for (int i = 0; i < nums1.length || i < nums2.length; i++) {
String str1 = i < nums1.length ? nums1[i] : "0";
String str2 = i < nums2.length ? nums2[i] : "0";
long num1 = 0;
for (int j = 0; j < str1.length(); j++) {
num1 = num1 * 10 + (str1.charAt(j) - '0');
}
long num2 = 0;
for (int j = 0; j < str2.length(); j++) {
num2 = num2 * 10 + (str2.charAt(j) - '0');
}
if (num1 > num2) {
return 1;
}
if (num1 < num2) {
return -1;
}
}
return 0;
}
}
26.二叉树的层序遍历
解题思路
- 如果 res 结果集的大小小于 depth ,说明此时 res 还在上一层,建立数组 row 并将 depth 层的节点的值添加到 row
- 否则节点为空直接返回
- 然后递归处理子树
代码实现
public class Solution {
/**
*
* @param root TreeNode类
* @return int整型ArrayList<ArrayList<>>
*/
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
public void traverse(TreeNode root, int depth) {
if (root != null) {
if (res.size() < depth) {
ArrayList<Integer> temp = new ArrayList<>();
res.add(temp);
temp.add(root.val);
} else {
ArrayList<Integer> temp = res.get(depth - 1);
temp.add(root.val);
}
} else {
return;
}
traverse(root.left, depth + 1);
traverse(root.right, depth + 1);
}
public ArrayList<ArrayList<Integer>> levelOrder(TreeNode root) {
// write code here
if (root == null) {
return res;
}
traverse(root, 1);
return res;
}
}
27.按之字形打印二叉树
解题思路
设置一个标志位,通过每遍历一层就取反的方式来判断是否将该层的节点的值反转
代码实现
public class Solution {
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
TreeNode head = pRoot;
if (head == null) {
return res;
}
//辅助队列,帮助层序遍历
Queue<TreeNode> temp = new LinkedList<>();
temp.offer(head);
TreeNode p;
Boolean flag = true;
while (!temp.isEmpty()) {
int n = temp.size();
//存储一行的节点的值
ArrayList<Integer> row = new ArrayList<>();
flag = !flag;
for (int i = 0; i < n; i++) {
p = temp.poll();
row.add(p.val);
if (p.left != null) {
temp.offer(p.left);
}
if (p.right != null) {
temp.offer(p.right);
}
}
if (flag) {
Collections.reverse(row);
}
res.add(row);
}
return res;
}
}
28.二叉树的最大深度
解题思路
记录左孩子的深度,记录右孩子的深度,返回 max(左孩子的深度,右孩子的深度)+ 根节点的深度(1)
代码实现
public class Solution {
/**
*
* @param root TreeNode类
* @return int整型
*/
public int maxDepth(TreeNode root) {
// write code here
if (root == null) {
return 0;
}
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
return Math.max(leftDepth, rightDepth) + 1;
}
}
29.二叉树和为某一值得路径-1
解题思路
写个出口条件,当左孩子为空,有孩子为空,且 sum 与路径和的差为 0 时,返回 true , 如果遇到 root 为 null 的情况,返回 false
代码实现
public class Solution {
/**
*
* @param root TreeNode类
* @param sum int整型
* @return bool布尔型
*/
public boolean hasPathSum(TreeNode root, int sum) {
// write code here
if (root == null) {
return false;
}
if (root.left == null && root.right == null && sum - root.val == 0) {
return true;
}
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
}
42.用两个栈实现队列
解题思路
- 入栈的话就正常入栈
- 出栈的话先将元素放在另一个栈中过一遍,达到一个逆序的效果,然后将栈顶元素出栈,之后将未出栈的元素重新压回最开始的栈,回复顺序,为下次出栈做好准备
代码实现
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
int res = stack2.pop();
while (!stack2.isEmpty()) {
stack1.push(stack2.pop());
}
return res;
}
}
43.包含min的栈
解题思路
依然是双栈法,第一个栈正常进出和查看栈顶,第二个栈专门用来存储min
代码实现
public class Solution {
Stack<Integer> s1 = new Stack<>();
Stack<Integer> s2 = new Stack<>();
public void push(int node) {
s1.push(node);
if (s2.isEmpty() || s2.peek() > node) {
s2.push(node);
} else {
s2.push(s2.peek());
}
}
public void pop() {
s1.pop();
s2.pop();
}
public int top() {
return s1.peek();
}
public int min() {
return s2.peek();
}
}
44.有效括号序列
解题思路
当遍历到‘(’‘【’‘{’的时候往栈中压入其对应的另一半,当栈为空或者出栈的括号与 s 对应位置上的不一致,就返回 false ,否则最后再判断一次栈是否非空, 空了就返回 true ,不空说明有多余的括号,依然要返回 false
代码实现
public class Solution {
/**
*
* @param s string字符串
* @return bool布尔型
*/
Stack<Character> stack = new Stack<>();
public boolean isValid(String s) {
// write code here
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stack.push(')');
} else if (s.charAt(i) == '[') {
stack.push(']');
} else if (s.charAt(i) == '{') {
stack.push('}');
} else if (stack.isEmpty() || stack.pop() != s.charAt(i)) {
return false;
}
}
return stack.isEmpty();
}
}
66.最长公共子串
解题思路
- 用
dp[i][j]
表示 str1[…i] 和 str2[…j] 的公共子串长度 - 状态转移
dp[i][j] = dp[i - 1][j - 1] + 1;
dp[i][j] = 0;
- 最后遍历dp数组找出一个最大值返回即可
代码实现
public class Solution {
/**
* longest common substring
* @param str1 string字符串 the string
* @param str2 string字符串 the string
* @return string字符串
*/
public String LCS(String str1, String str2) {
// write code here
int m = str1.length(), n = str2.length();
//str1[...i] , str2[...j]两个字符串的最长公共子串是 dp[i][j]
int[][] dp = new int[m + 1][n + 1];
//维护一个截取位置的结尾
int pos = 0;
//维护一个最大值
int max = 0;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = 0;
}
if (dp[i][j] > max) {
max = dp[i][j];
pos = i - 1;
}
}
}
return str1.substring(pos - max + 1, pos + 1);
}
}
67.不同路径的数目-1
解题思路
简单题,写出状态转移方程就结束了
代码实现
public class Solution {
/**
*
* @param m int整型
* @param n int整型
* @return int整型
*/
public int uniquePaths(int m, int n) {
// write code here
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
//base case
//处理边缘条件,第一行和第一列只能有一种到达办法
if (i == 1 || j == 1) {
dp[i][j] = 1;
continue;
}
dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
}
}
return dp[m][n];
}
}
68.矩阵的最小路径和
解题思路
注意 dp[0][0]
和第一行,第一列都需要经过特殊处理,然后剩下的就是常规的dp
代码实现
public class Solution {
/**
*
* @param matrix int整型二维数组 the matrix
* @return int整型
*/
public int minPathSum(int[][] matrix) {
// write code here
int n = matrix.length;
int m = matrix[0].length;
int[][] dp = new int[n + 1][m + 1];
//base case
dp[0][0] = matrix[0][0];
for (int i = 1; i < n; i++) {
dp[i][0] = matrix[i][0] + dp[i - 1][0];
}
for (int j = 1; j < m; j++) {
dp[0][j] = matrix[0][j] + dp[0][j - 1];
}
for (int i = 1; i < n; i++) {
for (int j = 1; j < m; j++) {
dp[i][j] = matrix[i][j] + Math.min(dp[i - 1][j], dp[i][j - 1]);
}
}
return dp[n - 1][m - 1];
}
}
69.把数字翻译成字符串
解题思路
此题对于面试意义不大,博主就看看思路就过了
代码实现(搬运)
public class Solution {
/**
* 解码
* @param nums string字符串 数字串
* @return int整型
*/
public int solve(String nums) {
// write code here
if (nums.equals("0")) {
return 0;
}
if (nums.equals("10") || nums.equals("20")) {
return 1;
}
for (int i = 1; i < nums.length(); i++) {
if (nums.charAt(i) == '0') {
if (nums.charAt(i - 1) != '1' && nums.charAt(i - 1) != '2') {
return 0;
}
}
}
int[] dp = new int[nums.length() + 1];
Arrays.fill(dp, 1);
for (int i = 2; i <= nums.length(); i++) {
if ((nums.charAt(i - 2) == '1' && nums.charAt(i - 1) != '0') ||
(nums.charAt(i - 2) == '2' && nums.charAt(i - 1) > '0' && nums.charAt(i - 1) < '7')) {
dp[i] = dp[i - 1] + dp[i - 2];
} else {
dp[i] = dp[i - 1];
}
}
return dp[nums.length()];
}
}
70.兑换零钱
解题思路
背包问题前缀题,很经典,建议背下来
代码实现
public class Solution {
/**
* 最少货币数
* @param arr int整型一维数组 the array
* @param aim int整型 the target
* @return int整型
*/
public int minMoney(int[] arr, int aim) {
// write code here
if (aim < 1) //⼩于1的都返回0
return 0;
int[] dp = new int[aim + 1];
Arrays.fill(dp, aim + 1); //dp[i]表示凑⻬i元最少需要多少货币数
dp[0] = 0;
for (int i = 1; i <= aim; i++) { //遍历1-aim元
for (int j = 0; j < arr.length; j++) { //每种⾯值的货币都要枚举
if (arr[j] <= i) //如果⾯值不超过要凑的钱才能⽤
dp[i] = Math.min(dp[i], dp[i - arr[j]] + 1); //维护最⼩值
}
}
return dp[aim] > aim ? -1 : dp[aim]; //如果最终答案⼤于aim代表⽆解
}
}
71.最长上升子序列
解题思路
- 以 arr[…i] 结尾的最长上升子序列的长度为dp[i]
- 通过遍历的方式找到当前以 i 结尾的数组的最长子序列
- 遍历 dp 拿到最大值并返回
代码实现
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 给定数组的最长严格上升子序列的长度。
* @param arr int整型一维数组 给定的数组
* @return int整型
*/
public int LIS(int[] arr) {
// write code here
//以 arr[...i] 结尾的最长上升子序列的长度为dp[i]
int n = arr.length;
int[] dp = new int[n];
//base case
Arrays.fill(dp, 1);
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (arr[j] < arr[i]) {
dp[i] = Math.max(dp[i], 1 + dp[j]);
}
}
}
int res = 0;
for (int i = 0; i < dp.length; i++) {
res = Math.max(res, dp[i]);
}
return res;
}
}
72.连续子数组的最大和
解题思路
- 以下标i结尾的子数组的最大和为dp[i]
- 每次遇到新的元素要么是它本身,要么是它加上前面的最大和,取一个max
- 遍历数组找到最大值
代码实现
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
int n = array.length;
//以下标i结尾的子数组的最大和为dp[i]
int[] dp = new int[n];
//base case
dp[0] = array[0];
for (int i = 1; i < n; i++) {
dp[i] = Math.max(array[i], dp[i - 1] + array[i]);
}
int res = dp[0];
for (int i = 0; i < dp.length; i++) {
res = Math.max(res, dp[i]);
}
return res;
}
}