常见面试算法题
1. 判断链表是否有环
快慢指针法
import jianzhiOffer.ListNode; //判断链表是否有环 public class HasCycle { public boolean hasCycle(ListNode head){ /*快慢指针法*/ if (head == null){ return false; } ListNode slowPoint = head; ListNode fastPoint = head.next; while (slowPoint != fastPoint){ if (fastPoint == null || slowPoint==null){ return false; } slowPoint = slowPoint.next; fastPoint = fastPoint.next.next; } return true; } }
2. 合并有序链表
import jianzhiOffer.ListNode;
public class MergeTwoList {
public static void main(String[] args) {
ListNode head1 = new ListNode(1);
ListNode node2 = new ListNode(3);
ListNode node3 = new ListNode(4);
ListNode node4 = new ListNode(7);
ListNode head2 = new ListNode(2);
ListNode node22 = new ListNode(5);
ListNode node23 = new ListNode(7);
ListNode node24 = new ListNode(9);
node3.next = node4;
node2.next = node3;
head1.next = node2;
node23.next = node24;
node22.next = node23;
head2.next = node22;
// ListNode newHead = merge(head1, head2);
ListNode newHead = merge(head1,null);
while (newHead != null){
System.out.println(newHead.val);
newHead = newHead.next;
}
}
public static ListNode merge(ListNode head1,ListNode head2){
if (head1==null || head2==null){
return head1==null?head2:head1;
}
ListNode p1 = head1;
ListNode p2 = head2;
ListNode dummyHead = new ListNode();
ListNode preNode = dummyHead;
while (p1 != null && p2 != null){
preNode.next = p1.val<=p2.val ? p1:p2;
preNode = preNode.next;
if (p1.val <= p2.val){
p1 = p1.next;
}else {
p2 = p2.next;
}
}
preNode.next = p1==null?p2:p1;
return dummyHead.next;
}
}
3. 判断括号序列是否合法
leetcode 20
辅助栈,左括号入栈,碰到右括号则将栈顶元素出栈,看看是不是匹配,不匹配返回false,匹配则继续,如果遍历到最后一个括号,栈正好空了,说明整个字符串合法
public class ValidParentheses { /*合法的括号字符串*/ public static void main(String[] args) { String str1 = "()"; String str2 = "(){}[]"; String str3 = ""; String str4 = "{[)]}"; System.out.println(isValidParentheses(str1)); System.out.println(isValidParentheses(str2)); System.out.println(isValidParentheses(str3)); System.out.println(isValidParentheses(str4)); } private static boolean isValidParentheses(String parentheses){ Map<String ,String> assistMap = new HashMap<>(); assistMap.put(")","("); assistMap.put("]","["); assistMap.put("}","{"); Stack<String> assistStack = new Stack<>(); for (int i=0; i<parentheses.length(); i++){ String s = parentheses.substring(i,i+1); if (assistMap.containsKey(s)){ //如果是右括号,判断栈顶是不是对应的左括号 String top = assistStack.isEmpty()?"#":assistStack.pop(); if (!top.equals(assistMap.get(s))){ return false; } }else { //如果是非右括号,入栈 assistStack.push(s); } } return assistStack.isEmpty(); } }
4. 跳台阶
可以从第n-1个台阶跳一步到,跳上第n个台阶,或者从第n-2个台阶跳2步到n,所以调上第n个台阶的方法等于跳上第n-1个台阶的方法加上跳上第n-2个台阶的方法,符合斐波那契数列,所以这个问题等同于求斐波那契的第n项
5. 求数组中的连续子数组的最大累加和 剑指offer42
设置一个max,初始化为一个非常小的值,再设置一个tempSum,用于试探累积下一个元素的结果
tempSum逐个累加数组元素,每加上一个,比较tempSum的值是不是大于max,如果是,更新max;
还要比较tempSum是不是变成了负数,如果是,说明这次加到tempSum的元素太小了,子数组里不能包括它,不然绝对得不到最大累加和,所以把tempSum设为0,从下一个元素开始重新累加。
public class FindGreatestSumOfSubArray { /*找到数组中连续子数组的最大累加和*/ public static void main(String[] args) { System.out.println(findGreatestSumOfSubArray(new int[]{1, -2, 3, 5, -2, 6, -1})); System.out.println(findGreatestSumOfSubArray(new int[]{0,0,0,0,0,3,0,0,0})); } private static int findGreatestSumOfSubArray(int[] nums){ int max = Integer.MIN_VALUE; int tempSum = 0; int length = nums.length; for (int i=0; i<length; i++ ){ tempSum += nums[i]; max = Math.max(tempSum,max); if (tempSum < 0){ tempSum = 0; } } return max; } }
6. 求二叉树最大深度或最小深度
递归,如果不是叶子节点,则返回节点左右子树深度最大的那个,最大深度和最小深度可以同一求法,只不过最小深度是返回较小的那个深度
public class FindMDepth { /*找一棵树的最大深度,一棵树的最大深度等于其左子树和右子树的最大深度+1 * 同理,一棵树的最小深度等于其左子树和右子树最小深度+1 * 所以,可以递归遍历一棵树的所有节点的左右子树,返回比较大或者比较小的深度*/ public static void main(String[] args) { } private static int findMaxDeprth(TreeNode root){ if (root == null){ return 0; } int leftDepth = findMaxDeprth(root.leftChild); int rightDepth = findMaxDeprth(root.rightChild); return Math.max(leftDepth, rightDepth)+1; } private static int findMinDeprth(TreeNode root){ if (root == null){ return 0; } int leftDepth = findMaxDeprth(root.leftChild); int rightDepth = findMaxDeprth(root.rightChild); return Math.min(leftDepth, rightDepth)+1; } }
7. 找到一个数组中的两个数,他们的和等于target
用HashMap做,但是注意是数组元素为Key,下标为value。遍历数组,先求target-当前元素,然后看看Map里面有没有以这个差为key的键值对,如果有,返回,没有的话,存入<元素,下标>。
import java.util.HashMap; import java.util.Map; public class TwoSum { /*在一个数组中找出两个数,使他们的和等于一个目标值,返回两个数在数组中的出现顺序,注意不是下标 * 例如: 给出的数组为 {2, 7, 11, 15},目标值为9 输出 index1=1, index2=2 */ public static void main(String[] args) { } private static int[] twoSum(int[] numbers,int target){ Map<Integer,Integer> numberMap = new HashMap<>(); int[] result = new int[]{0,0}; int length = numbers.length; for (int i=0; i<length; i++){ int anotherNum = target-numbers[i]; if (numberMap.containsKey(anotherNum)){ result[1] = i+1; result[0] = numberMap.get(anotherNum)+1; break; }else { numberMap.put(numbers[i],i); } } return result; } }
8. 二叉树镜像
前序遍历二叉树,交换每一个节点的左右子树
import jianzhiOffer.ListNode; public class MirrorOfBinaryTree { /*求一刻二叉树的镜像*/ private static TreeNode mirrorOfBinaryTree(TreeNode root){ if (root == null) return root; //前序遍历,交换每个结点的左右子树 TreeNode tempNode = root.leftChild; root.leftChild = root.rightChild; root.rightChild = tempNode; mirrorOfBinaryTree(root.leftChild); mirrorOfBinaryTree(root.rightChild); return root; } }
9. 找出数组中只出现了一次的数
可以用Set实现,如果,把每个数add到set中,如果返回true,说明set中没有添加过这个数,如果返回false,说明这个数已经存在于set中,即这个数重复了,然后从set中remove这个重复的数
import java.util.Arrays; import java.util.HashSet; import java.util.Set; public class OnceOcurredNum { public static void main(String[] args) { int[] nums = new int[]{0,1,3,4,3,6,13,4,9,23}; System.out.println(Arrays.toString(getOnceOcurredNum(nums))); } private static int[] getOnceOcurredNum(int[] nums){ Set<Integer> numSet = new HashSet<>(); for (int number : nums) { if (!numSet.add(number)){ numSet.remove(number); }else{ numSet.add(number); } } return numSet.stream().mapToInt(Integer::intValue).toArray(); } }
10. 二叉搜索树的第k小的节点
中序遍历二叉树,得到的结果就是节点的从小到大排序,那么第k-1个节点就是第k小的节点
public class KthNodeOfBinaryTree { int index = 0; private TreeNode findKthNodeOfBinaryTree(TreeNode pRoot, int k){ TreeNode result = null; //遍历左节点 if (pRoot.left != null){ result = findKthNodeOfBinaryTree(pRoot.left,k); } //如果當前節點的左节点为null,则遍历当前节点 index++; if (k==index) { result = pRoot; } // 如果还未找到第k个节点,且右节点不为空,才去遍历右节点 if (result==null && pRoot.right != null){ result = findKthNodeOfBinaryTree(pRoot.right,k); } return result; } private TreeNode KthNode(TreeNode root, int k){ if (root == null || k<=0){ return null; } return findKthNodeOfBinaryTree(root,k); } }
11. 判断一个链表是否为回文结构
先看看怎么找一个链表的中间节点
设置两个指针,一快一慢,慢的每次走一步,快的每次走两步,当快指针为null(偶数节点)或者快指针的next为null(奇数节点)时,此时慢指针即指向中间节点。
public ListNode middleNode(ListNode head) { ListNode fast = head; ListNode slow = head; //每次移动之前,都要判断fast是否为null,或者fast.next是否为null,才能移动 while(fast != null && fast.next != null){ fast = fast.next.next; slow = slow.next; } /*退出循环后,快指针有两种情况 * 1、链表偶数个节点,快指针指向null,即尾结点的next,慢指针指向第n/2+1个节点 * 2、链表奇数个节点,快指针指向尾结点,不为null,慢指针指向第(n+1)/2个节点*/ return slow; }
两个方法:
把所有节点依次压入栈中,然后挨个出栈,与链表节点比较,如果都相等,则是回文结构
快慢指针法,结合栈实现,设置快慢指针,找到中间节点,然后把中间节点开始的节点全部入栈,这样就不用把所有节点全部入栈
/*快慢指针加反转链表实现, AC*/ public boolean isPalindrome2(ListNode head){ //空链表或者单节点链表,返回true if (head==null ){ return true; } if (head.next == null){ return true; } //快慢指针 ListNode fastPoint = head; ListNode slowPoint = head; //找中间节点 ListNode midNode = null; while (fastPoint!=null && fastPoint.next!=null){ fastPoint = fastPoint.next.next; slowPoint = slowPoint.next; } midNode = slowPoint; /*退出循环后,快指针有两种情况 * 1、链表偶数个节点,快指针指向null,即尾结点的next * 2、链表奇数个节点,快指针指向尾结点,不为null*/ ListNode newHead = null; if(fastPoint == null){ //如果是偶数个节点 newHead = reverseList(midNode); }else { //如果是奇数个节点 newHead = reverseList(midNode.next); } //开始比较 while (newHead != null){ if (newHead.val!=head.val){ return false; }else { newHead = newHead.next; head = head.next; } } return true; } public static ListNode reverseList(ListNode head){ if(head == null || head.next==null){ return head; } ListNode firstNodeOfNewList = null; ListNode nextNode = null; ListNode curNode = head; //当前遍历的节点 while (curNode != null){ // 1、取出当前节点的next nextNode = curNode.next; //2、把当前节点接到新链表上 curNode.next = firstNodeOfNewList; //3、让当前节点成为新链表的第一个节点 firstNodeOfNewList = curNode; //4、nextNode成为下一个遍历的节点 curNode = nextNode; } //循环退出后,curNode指向的是原来链表尾部的null节点,firstNodeOfNewList指向新结点的头部,也是原节点的尾部 return firstNodeOfNewList; }
12. 股票买入和卖出能获得的最大利润
遍历每一个数组,然后找到之前最低的股价,两者之差就是在现在这天卖出股票能获得的最大利润。有一个问题是怎么获得历史股价的最低值,我们需要设置一个min变量,它存储着当前的历史最低股价,还需要保存一个maxProfit,它保存着历史最大利润,每次循环都判断是否更新这两个值。
import java.util.Map; public class MaxProfit { /*买卖股票能得到的最大利润 * 基本思路:每天都算一下,当天的股价减去历史最低点的股价,得到的利润是不是最大利润,是则更新最大利润*/ public int maxProfit(int[] prices){ //历史最低股价 int lowestPrice = Integer.MAX_VALUE; int maxProfit = 0; //逐天遍历,计算利润,当天卖出的最大利润 for (int price : prices) { //判断是否要更新历史最低股价 lowestPrice = Math.min(price,lowestPrice); //判断是否要更新最大利润 maxProfit = Math.max(price - lowestPrice, maxProfit); } return maxProfit; } }
13. 设计一个有getMin()功能的栈
利用两个栈来实现,其中一个栈allDataStack正常存储元素,另一个栈minStack的栈顶始终是栈中最小元素。
- 入栈:入栈前先判断待入栈元素是不是小于或等于allDateStack的栈顶元素,是的话需要存入minStack,不是的话,只入allDataStack
- 出栈:需要注意,如果出栈元素值等于minStack元素的栈顶,那么需要把minStack的栈顶也弹出去,minStack不能包含allData中不存在的元素
- getMin:只peek()minData,不pop
14. 合并两个有序数组,比如A和B,两个数组合并到A中,A的空间足够大
从两个数组的最后一个元素开始比较,A[i]和B[j]中比较大的数放到A数组的尾部,如果i活着j中某一个下标变成了0,就把另一个数组剩下的元素全部放到A中
15. 斐波那契数列
递归实现
- 循环实现
16. 反转数字
leetCode第7题
除10取余,得到最后一位数字,然后result = result*10+最后一位数,注意溢出
17. 判断二叉树有没有一条从根节点到叶子节点的路径的和等于一个目标值
递归遍历每一条路径,每遍历一个节点,用目标值sum减去节点值,如果到了叶子节点(leftnull && rightnull),sum==0,则说明存在这样的路径。
18. 判断对称二叉树
分别用前序遍历和对称前序遍历两种方法来遍历二叉树,如果最后得到的序列是对称的,则是对称二叉树,需要注意:就算节点为null,也需要把节点加入到序列中。
19. 找出在数组中出现次数超过数组长度一半的数字 剑指offer 39
- 方法1:随机快排方法,不懂
- 方法2:从头开始遍历数组,我们需要在每次遍历时修改保存两个量,一个是数组中的数字,另一个是次数。如果当前遍历到的数字和现在保存的数字相同,则把次数加1,如果不同,减1,如果减到次数为0了,就把保存的数字换成当前遍历到的数字。最后肯定只有出现次数超过一半的那个数字最终的次数能够大于0
20. 反转链表
public static
21. 排序算法
1. 冒泡
package sort;
import java.util.Arrays;
/*冒泡排序需要对每一个元素进行冒泡,找到它能达到的最后的位置*/
public class BubbleSort {
public static void main(String[] args) {
int[] nums = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
bubbleSort(nums);
System.out.println(Arrays.toString(nums));
}
private static void bubbleSort(int[] nums){
if (nums == null || nums.length <= 1){
return;
}
int length = nums.length;
for (int i=0; i<length; i++){
for (int j=0; j< length-i-1; j++){
if (nums[j]>nums[j+1]){
//如果当前数字大于他的下一个数,那么就交换他俩的位置
int temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
}
private static void swap(int curNum, int num) {
int temp = curNum;
curNum = num;
num = temp;
}
}
2. 插入
package sort;
import java.util.Arrays;
public class InsertSort {
public static void main(String[] args) {
int[] nums = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
insertSort(nums);
System.out.println(Arrays.toString(nums));
}
private static void insertSort(int[] nums){
if (nums == null || nums.length <= 1){
return;
}
int length = nums.length;
int temp = 0;
// i从1 到 数组末尾
for (int i=1; i<length; i++){
// j 从i到1
for (int j=i; j>0; j--){
//比较当前数和前一个数,如果比前一个数小,交换位置
if (nums[j] < nums[j-1]){
//交换两数
temp = nums[j];
nums[j] = nums[j-1];
nums[j-1] = temp;
}
}
}
}
}
3. 归并
package sort;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] nums = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
// int[] nums = {3, 44, 38, 5, 47, 15};
mergeSort(nums);
System.out.println(Arrays.toString(nums));
}
private static int[] assistArray = null;
private static void mergeSort(int[] nums){
int leftIndex = 0;
int rightIndex = nums.length-1;
int midIndex = (leftIndex+rightIndex)/2;
assistArray = new int[nums.length]; //辅助数组
mergeSort(nums,leftIndex,rightIndex);
}
private static void mergeSort(int[] nums, int leftIndex, int rightIndex) {
if (leftIndex < rightIndex){
//不断递归,拆分数组,直到数组中只有一个元素
//1、计算中间坐标
int midIndex = (leftIndex+rightIndex)/2;
//2、继续拆分左半边
mergeSort(nums,leftIndex,midIndex);
//3、继续拆分右半边
mergeSort(nums,midIndex+1,rightIndex);
//两边都拆成了只有一个元素的数组,就可以开始合并了
merge(nums,leftIndex,midIndex,rightIndex);
}
}
private static void merge(int[] nums, int leftIndex, int midIndex, int rightIndex) {
int p1 = leftIndex;
int p2 = midIndex+1;
int k = leftIndex;
//合并两个数组
while (p1<=midIndex && p2<=rightIndex){
assistArray[k++] = nums[p1]<nums[p2]?nums[p1++]:nums[p2++];
}
while (p1<=midIndex){
assistArray[k++] = nums[p1++];
}
while (p2<=rightIndex){
assistArray[k++] = nums[p2++];
}
//把排好序的这部分放到原数组中
for (int i=leftIndex; i<=rightIndex; i++){
nums[i] = assistArray[i];
}
}
}
4. 快排
package sort;
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] nums = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
// int[] nums = {3, 44, 38, 5, 47, 15};
quickSort(nums);
System.out.println(Arrays.toString(nums));
}
private static void quickSort(int[] nums){
quickSort(nums,0,nums.length-1);
}
private static void quickSort(int[] nums,int low,int high){
if (low >= high){
return;
}
int pLeft=low; //
int pRight=high;
int curNum = nums[low]; //最左边的数作为基准数
while (pLeft<pRight){
//先移动右边的指针
while (nums[pRight] >= curNum && pLeft<pRight){
pRight--;
}
//移动左边的指针
while (nums[pLeft] <= curNum && pLeft<pRight){
pLeft++;
}
if (pLeft < pRight){
//这是左边指针指向了比基准数大的数,右边的指针指向了比基准数小的数
//交换两个数
int temp = nums[pRight];
nums[pRight] = nums[pLeft];
nums[pLeft] = temp;
}
}
//外部的while退出时,说明 pLeft和 pRight相遇
//交换基准数与相遇处的元素
nums[low] = nums[pLeft];
nums[pLeft] = curNum;
//继续递归对左边数组排序
quickSort(nums,low,pLeft-1);
//递归对右边数组排序
quickSort(nums,pLeft+1,high);
}
}
5. 选择
package sort;
import java.util.Arrays;
/*快速排序,从第一个元素开始,找数组中最小的元素,放到第一个位置,然后找第二小的放到第二个位置*/
public class SelectSort {
public static void main(String[] args) {
int[] nums = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
selectSort(nums);
System.out.println(Arrays.toString(nums));
}
private static void selectSort(int[] nums){
if (nums == null || nums.length<=1){
return;
}
for (int i=0; i<nums.length; i++){
int minIndex = i;
for (int j=i; j<nums.length; j++){
if (nums[j] < nums[minIndex]){
minIndex = j;
}
}
if (i != minIndex){
//交换nums[i]和num[minIndex]
nums[i] = nums[minIndex]-nums[i];
nums[minIndex] = nums[minIndex]-nums[i];
nums[i] = nums[minIndex] + nums[i];
}
}
}
}