在面试中我们常常会碰到手写代码的情况,考验我们的临场反应和平时积累数据结构与算法的能力,在这里我整理一下在面试中那些高频出现的面试算法题,欢迎大家一起学习,同时这篇博客将会只有未完待续,没有完结,欢迎大家将自己的一些见解和学习心得评论交流~
首先这篇博客我先粗略的分为如下几类:
1.排序算法
2.二叉树问题
3.链表问题
4.字符串问题
目录
1.1 快速排序
package com.ma.sort;
/**
* 快速排序
* 快速排序(英语:Quicksort),又稱劃分交換排序(partition-exchange sort),簡稱快排,一種排序算法,最早由
* 東尼·霍爾提出。在平均狀況下,排序 n個項目要 O(n\log n)
* O(n\log n)大O符号)次比較。在最壞狀況下則需要 O(n^{2})
* O(n^{2})次比較,但這種狀況並不常見。事實上,快速排序 Theta (n\log n)
* Theta (n\log n)通常明顯比其他演算法更快,因為它的內部循环(inner loop)可以在大部分的架構上很有效率地達成。
*/
/**
* 时间复杂度:O(nlog2n)
*/
/**
* 空间复杂度:O(1)
*/
/**
* 不稳定
*/
public class Quicksorting {
public static void quickSort(int[] a, int low, int hi){
if(low < hi){
int middle = getMiddle(a, low, hi);
quickSort(a, 0, middle - 1);
quickSort(a, middle + 1, hi);
}
}
public static int getMiddle(int[] a, int low, int hi){
int key = a[low];
while(low < hi){
while(low <hi && a[hi] >= key){
hi--;
}
a[low] = a[hi];
while(low < hi && a[low] <= key){
low++;
}
a[hi] = a[low];
}
a[low] = key;
return low;
}
public static void main(String[] args) {
int[] a = {3,2,5,8,4,7,6,9};
quickSort(a, 0, a.length - 1);
for (int i : a){
System.out.print(i + " ");
}
}
}
1.2 归并排序
package com.ma.sort;
/**
* 归并排序
* 要将一个数组排序可以先将它递归的分成两部分进行排序,然后将结果归并起来。
*/
/**
* 时间复杂度:O(nlog2n)
*/
/**
* 空间复杂度:O(n)
*/
/**
* 稳定
*/
public class Mergesort {
private static int[] ca;//归并时候需要的辅助数组
public static void sort(int[] a){
ca = new int[a.length];
sort(a,0,a.length - 1);
}
private static void sort(int[] a, int lo, int hi){
//将数组a[lo...hi]排序
if(hi <= lo){
return;
}
int mid = lo + (hi - lo)/2;
sort(a, lo, mid);//将左半边排序
sort(a,mid + 1, hi);//将右半边排序
merge(a, lo, mid, hi);
}
public static void merge(int[] a,int lo, int mid, int hi){
//将a[lo...mid]和a[mid+1...hi]归并
int i = lo,j = mid + 1;
for(int k = lo; k <= hi; k++){
ca[k] = a[k];
}
for(int k = lo; k <= hi; k++){
if(i > mid) a[k] = ca[j++];
if(j > hi) a[k] = ca[i++];
else if(ca[j] < ca[i]) a[k] = ca[j++];
else a[k] = ca[i++];
}
}
public static void main(String[] args) {
int[] a = {3,2,5,8,4,7,6,9};
sort(a);
for(int i : a){
System.out.print(i + " ");
}
}
}
1.3 堆排序
package com.ma.sort;
/**
* 堆排序
* 堆排序分为两个阶段。在堆的构建过程中我们将原始数组重新组织安排进一个堆中;然后在下沉排序阶段,我们从堆中按照
* 递减顺序取出所有元素并得到排序结果。
*/
/**
* 时间复杂度:O(nlog2n)
*/
/**
* 空间复杂度:O(1)
*/
/**
* 不稳定
*/
public class Heapsorting {
/*
创建最小堆
*/
public static void createLittleHeap(int[] a, int last){
for(int i = (last - 1) / 2; i >= 0; i--){//找到最后一个叶子节点的双亲节点
//保存当前正在判断的节点
int parent = i;
//若当前节点的左节点存在,即子节点存在
while(2 * parent + 1 <= last){
//bigger指向左节点
int bigger = 2 * parent + 1;
//说明存在右节点
if(bigger < last){
//右子节点大于左子节点时
if(a[bigger + 1] > a[bigger]){
bigger = bigger + 1;
}
}
if(a[parent] <= a[bigger]){
swap(a, parent, bigger);
parent = bigger;
}else{
break;
}
}
}
}
public static void swap(int[] a, int i, int j){
a[i] = a[i] + a[j];
a[j] = a[i] - a[j];
a[i] = a[i] - a[j];
}
public static void main(String[] args) {
int[] a = {3,2,5,8,4,7,6,9};
for(int i = 0; i < a.length; i++){
createLittleHeap(a, a.length - 1 - i);
swap(a, 0, a.length -1 -i);
}
for(int i : a){
System.out.print(i + " ");
}
}
}
1.4 冒泡排序
package com.ma.sort;
import com.sun.org.apache.bcel.internal.generic.SWAP;
/**
* 冒泡排序
*/
/**
* 时间复杂度:O(n²)
*/
/**
* 空间复杂度:O(1)
*/
/**
* 不稳定
*/
public class Bubble {
public static void sort(int[] array){
for(int i = 0; i < array.length - 1; i++){
for(int j = 0; j < array.length - i-1; j++){
if(array[j] > array[j + 1]){
int temp = array[j];
array[j] = array[j + 1];
array[j +1] = temp;
}
}
}
}
public static void main(String[] args) {
int[] array = {3,2,5,8,4,7,6,9};
sort(array);
for(int a : array){
System.out.print(a+" ");
}
}
}
1.5 插入排序
package com.ma.sort;
/**
* 插入排序
* 通常人们整理桥牌的方法是一张一张来,将每一张排插入到其余已有牌的适当位置。
* 在计算机的实现中,为了给要插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位,这种算法
* 叫做插入排序。
*/
/**
* 时间复杂度:O(n²)
*/
/**
* 空间复杂度:O(1)
*/
/**
* 稳定
*/
public class Insertionsort {
public static void sort(int[] a){
int N = a.length;
for(int i = 1; i < N; i++){
for(int j = i; j > 0 && a[j] < a[j -1]; j--){
int tmp = a[j];
a[j] = a[j - 1];
a[j - 1] = tmp;
}
}
}
public static void main(String[] args) {
int[] a = {3,2,5,8,4,7,6,9};
sort(a);
for(int i : a){
System.out.print(i + " ");
}
}
}
1.6 选择排序
package com.ma.sort;
/**
* 选择排序
* 一种最简单的排序方法,首先,找到数组中最小的元素,其次,将它和数组第一个元素交换位置。
* 再次,在剩下的元素中找到最小的元素和第二个位置的元素进行交换。
*/
/**
* 时间复杂度:O(n²)
*/
/**
* 空间复杂度:O(1)
*/
/**
* 不稳定
*/
public class Selectionsorting {
public static void sort(int[] a){
for(int i = 0; i < a.length; i++){
for(int j = i +1; j < a.length; j++){
if(a[j] < a[i]){
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
}
}
public static void main(String[] args) {
int[] a = {3,2,5,8,4,7,6,9};
sort(a);
for (int i : a){
System.out.print(i+" ");
}
}
}
2.1 二叉树递归遍历
/**
* 二叉树先序递归遍历
*/
public class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value = data;
}
}
public void preOrderRecur(Node head){
if(head == null){
return ;
}
System.out.println(head.value+"");
preOrderRecur(head.left);
preOrderRecur(head.right);
}
/**
* 二叉树中序递归遍历
*/
public class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value = data;
}
}
public void inOrderRecur(Node head){
if(head == null){
return ;
}
inOrderRecur(head.left);
System.out.println(head.value+"");
inOrderRecur(head.right);
}
/**
* 二叉树后序递归遍历
*/
public class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value = data;
}
}
public void posOrderRecur(Node head){
if(head == null){
return ;
}
posOrderRecur(head.left);
posOrderRecur(head.right);
System.out.println(head.value+"");
}
2.2 二叉树非递归遍历
/**
* 非递归先序遍历
*/
public void preOrderUnRecur(Node head){
if(head!=null){
Stack<Node> stack = new Stack<Node>();
stack.add(head);
while(!stack.isEmpty()){
head = stack.pop();
System.out.println(head.value+"");
if(head.right!=null){
stack.push(head.right);
}
if(head.left!=null){
stack.push(head.left);
}
}
}
System.out.println("");
}
/**
* 非递归中序遍历
*/
public void preOrderUnRecur(Node head){
if(head!=null){
Stack<Node> stack = new Stack<Node>();
while(!stack.isEmpty()||head!=null){
if(head!=null){
stack.push(head);
head=head.left;
}else{
head=stack.pop();
System.out.println(head.value+"");
head=head.right;
}
}
}
System.out.println("");
}
/**
* 非递归后序遍历
*/
public void preOrderUnRecur(Node head){
if(head!=null){
Stack<Node> s1 = new Stack<Node>();
Stack<Node> s2 = new Stack<Node>();
s1.push(head);
while(!s1.isEmpty()){
head=s1.pop();
s2.push(head);
if(head.left!=null){
s1.push(head.left);
}
if(head.right!=null){
s1.push(head.right);
}
}
while(!s2.isEmpty()){
System.out.println(s2.pop().value+"");
}
}
System.out.println("");
}
2.3 “之”字型打印二叉树
package com.ma.twoforkedtree;
import java.util.LinkedList;
import java.util.Stack;
public class ZhiPrint {
public static void print(TreeNode root){
if(root == null){
return;
}
Stack<TreeNode> s1 = new Stack<>();//存放奇数行
Stack<TreeNode> s2 = new Stack<>();//存放偶数行
s1.push(root);
while(! s1.isEmpty()){
//弹出奇数行的元素
TreeNode node = s1.pop();
System.out.print(node.value + " ");
//判断该行是否结束
if(s1.isEmpty()){
System.out.print(" ");
}
//给s2中先左后右压入节点
if(node.left != null){
s2.push(node.left);
}
if(node.right != null){
s2.push(node.right);
}
}
while(! s2.isEmpty()){
//弹出偶数行的元素
TreeNode node = s2.pop();
System.out.print(node.value + " ");
//判断该行是否结束
if(s2.isEmpty()){
System.out.print(" ");
}
//给s1中先右后左压入节点
if(node.right != null){
s1.push(node.right);
}
if(node.left != null){
s1.push(node.left);
}
}
}
public static void main(String[] args) {
//用队列构建一个完全二叉树
LinkedList<TreeNode> q = new LinkedList<>();
int i = 1;
TreeNode root = new TreeNode(i++);
q.add(root);
while(!q.isEmpty() && i < 16){
TreeNode lchild = new TreeNode(i++);
TreeNode rchild = new TreeNode(i++);
TreeNode node = q.poll();
node.left = lchild;
node.right = rchild;
q.add(node.left);
q.add(node.right);
}
print(root);
}
}
class TreeNode{
int value;
TreeNode left;
TreeNode right;
public TreeNode(int value){
this.value = value;
}
}
3.1 反转单向链表
public class Node{
public int value;
public Node next;
public Node(int data){
this.value = data;
}
}
public Node reverseList(Node head){
Node pre = null;
Node next = null;
while(head != null){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
3.2 合并两个有序的单链表
public class Node{
public int value;
public Node next;
public Node(int data){
this.value = data;
}
}
public Node merge(Node head1, Node head2){
if(head1 == null || head2 == null){
return head1 != null ? head1 : head2;
}
Node head = head1.value < head2.value ? head1 : head2;
Node cur1 = head == head1 ? head1 : head2;
Node cur2 = head == head1 ? head2 : head1;
Node pre = null;
Node next = null;
while(cur1 != null && cur2 != null){
if(cur1.value <= cur2.value){
pre = cur1;
cur1 = cur1.next;
}else{
next = cur2.next;
pre.next = cur2;
cur2.next = cur1;
pre =cur2;
cur2 = next;
}
}
pre.next = cur1 == null ? cur2 : cur1;
return head;
}
4.1 最长无重复子串的长度
package com.ma.characterString;
/**
* 题目:
* 给定一个字符串 str,返回 str 最长无重复子串的长度。
* 举例:
* str = "abcd" 返回 4
* str = "aabcb",最长无重复子串为"abc",返回3。
*/
/**
* 解答:
* 如果 str 长度为 N,字符编码范围是M,本题可以做到时间复杂度为O(N),额外空间复杂度为O(M)。
*
* 1.在遍历str之前,先申请几个变量。哈希表 map,key表示某个字符,value为这个字符第一次出现的位置。
* 整型变量pre,如果当前遍历到字符str[i],pre表示必须以str[i - 1]字符结尾的情况下,最长无重复子串
* 开始位置的前一个位置,初始时pre = -1。整型变量len,记录每一个字符结尾的情况下,最长无重复子串
* 长度的最大值,初始时,len = 0。从左到右遍历字符串str,假设现在遍历到str[i],接下来求必须以str[i]
* 结尾的情况下,最长无重复子串的长度。
*
* 2.map(str[i])的值表示之前遍历中最近一次出现str[i]的位置,假设在a位置。想要求以str[i]结尾的最长无
* 重复子串,a位置必然不能包含进来,因为str[a]等于str[i]。
*
* 3.根据pre的定义,pre + 1 表示必须以str[i - 1]字符结尾的情况下,最长无重复子串开始的位置,假设在
* a位置,也就是说,以str[i -1 ]结尾的最长无重复子串是向左扩展到pre位置才停止的。
*
* 4.如果pre的位置在a的左边,因为str[a]的位置是不能包含进来的,而str[a+1...i-1]上是不重复的,所以
* 以str[i]结尾的最长无重复子串就是str[a+1...i]。如果pre的位置在a的右边,以str[i]结尾的最长无重复子串
* 是向左扩充到pre结束的。所以以str[i]结尾的最长无重复子串扩充到pre也会停止,而且str[pre+1... i-1]
*上肯定不含有str[i],s所以以str[i]结尾的最长无重复子串就是str[pre+1...i]
*
* 5.计算完长度后,pre位置和a位置哪一个在右边哪一个就作为新的pre值。然后计算下一个pre的值。所有长度
* 的最大值用len记录。
*/
public class Longestnonrepeatingsubstring {
public static int maxUnique(String str){
if(str == null || str == ""){
return 0;
}
char[] chas = str.toCharArray();
int[] map = new int[256];
for(int i = 0; i < 256; i++){
map[i] = -1;
}
int pre = -1;
int cur = 0;
int len = 0;
for(int i = 0; i != chas.length; i++ ){
pre = Math.max(pre, map[chas[i]]);
cur = i - pre;
len = Math.max(len, cur);
map[chas[i]] = i;
}
return len;
}
public static void main(String[] args) {
String str = new String("abcabcdebc");
System.out.println(maxUnique(str));
}
}
4.2 返回两个字符串的最长公共子串
package com.ma.characterString;
/**
* 题目:
* 给定两个字符串 str1 和 str2,返回两个字符串的最长公共子串。
*举例:
* str1 = "1AB2345CD",str2 = "12345EF",返回"2345"。
*/
/**
* 解答:
* 经典动态规划的方法可以做到时间复杂度为O(M*N),额外空间复杂度为O(M*N)。
* 首先需要生成动态规划表。生成大小为M*N的矩阵dp,行数为M,列数为N。dp[i][j]的含义是:
* 在必须把str1[i]和str2[j]当作公共子串最后一个字符的情况下,最长公共子串能有多长。
* 比如,str1 = "A1234B",str2 = "CD1234",dp[3][4]的含义是在必须把str1[3]和str2[4]当作公共子串
* 最长能有多长。这时候最长公共子串为"123"所以dp[3][4]为3。
* 再如,str1 = "A12E4B",str2 = "CD12F4",dp[3][4]的含义是必须把str1[3]和str2[4]当作公共子串的最后一个
* 字符的情况下,公共子串能有多长。显然这种情况不可能构成公共子串。dp[3][4]为0。
*/
/**
* 1.矩阵dp第一列即dp[0..M-1][0]。对某一个位置(i,0)来说,如果str[i] == str2[0],令dp[i][0] = 1,
* 否则令dp[i][0] = 0。
* 比如str1 = "ABAC" str2 = "A"。dp矩阵第一列上的值依次为dp[0][0] = 1,dp[2][0] = 0,dp[3][0] = 1,
* dp[3][0] = 0。
* 2.矩阵dp第一行dp[0][0...N-1]与步骤1同理。对某一个位置(0,j)来说,如果str1[0] == str2[j],令
* dp[0][j] = 1,否则令dp[0][j] = 0。
* 3.其他位置按照从左到右,再从上到下来计算,dp[i][j]的值只能有两种情况。
* 3.1如果str1[i] != str2[j]说明在必须把str1[i]和str2[j]作为公共子串的最后一个字符是不可能的,令
* dp[i][j] = 0。
* 3.2如果str1[i] == str[j]说明str1[i]和str2[j]可以作为公共子串最后一个字符,从最后一个字符能向
* 左扩多大的长度呢?就是dp[i-1][j-1]的值,所以令dp[i][j] = dp[i-1][j-1]+1。
*/
/**
* 生成动态规划表之后,得到最长公共子串是非常容易的。
*/
public class LongestPublicCharacterString {
public static int[][] getdp(char[] str1, char[] str2){
int[][] dp = new int[str1.length][str2.length];
for(int i = 0; i < str1.length; i++){
if(str1[i] == str2[0]){
dp[i][0] = 1;
}
}
for(int j = 0; j < str2.length; j++){
if(str2[j] == str1[0]){
dp[0][j] = 1;
}
}
for(int i = 1; i < str1.length; i++){
for(int j = 1; j < str2.length; j++){
if(str1[i] == str2[j]){
dp[i][j] = dp[i-1][j-1] + 1;
}
}
}
return dp;
}
public static String lcst1(String str1, String str2){
if(str1 == null || str2 == null || str1.equals("") || str2.equals("")){
return "";
}
char[] chs1 = str1.toCharArray();
char[] chs2 = str2.toCharArray();
int[][] dp = getdp(chs1,chs2);
int end = 0;
int max = 0;
for(int i = 0; i < chs1.length; i++){
for(int j = 0; j < chs2.length; j++){
if(dp[i][j] > max){
end = i;
max = dp[i][j];
}
}
}
return str1.substring(end - max + 1, end + 1);
}
public static void main(String[] args) {
String str1 = "abcbcbbc";
String str2 = "cbcbaaa";
System.out.println(lcst1(str1, str2));
}
}