一、数组之二分查找
二分查找在面试算法题目中占有的比例有很大一部分,特总结如下
1.基本的二分查找,时间复杂度,此部分代码略
2.基于二分查找求一个key的个数
/**
* 二分查找的变种题,统计有序数组中有多少个相同的数字
* */
public static void main(String[] args) {
int array[] = {1, 2, 3, 3, 3, 3, 4, 5};
System.out.println(getNum(array, array.length, 3));
}
public static int getNum(int array[], int length, int key) {
int count = 0;
if (array != null && length > 0) {
int first = getFirstIndex(array, key, length, 0, length - 1);
int last = getLastIndex(array, key, length, 0, length - 1);
if (first > -1 && last > -1) {
count = last - first + 1;
}
}
return count;
}
public static int getFirstIndex(int array[], int key, int length, int start, int end) {
if (start > end) {
return -1;
}
int middleIndex = (start + end) / 2;
int middleData = array[middleIndex];
if (middleData == key) {
if ((middleIndex > 0 && array[middleIndex - 1] != key) || middleIndex == 0) {
return middleIndex;
} else {
end = middleIndex - 1;
}
} else if (middleData > key) {
end = middleIndex - 1;
} else {
start = middleIndex + 1;
}
return getFirstIndex(array, key, length, start, end);
}
public static int getLastIndex(int array[], int key, int length, int start, int end) {
if (start > end) {
return -1;
}
int middleIndex = (start + end) / 2;
int middleData = array[middleIndex];
if (middleData == key) {
if ((middleIndex < length - 1 && key != array[middleIndex + 1]) || middleIndex == length - 1) {
return middleIndex;
} else {
start = middleIndex + 1;
}
} else if (middleData > key) {
end = middleIndex - 1;
} else {
start = middleIndex + 1;
}
return getLastIndex(array, key, length, start, end);
}
3.循环递增数组,查找一个key是否存在于数组中(二分查找的变种用法)
public static boolean isHasKey(int arr[],int key){
int low=0;
int high=arr.length-1;
while (low<=high){
int mid=(low+high)/2;
if (arr[mid]==key){
return true;
}
if (arr[low]<=arr[mid]){
if (arr[low]<=key&&key<arr[mid]){
high=mid-1;
}else {
low=mid+1;
}
}else {
if (arr[mid]<key&&key<=arr[high]){
low=mid+1;
}else {
high=mid-1;
}
}
}
return false;
}
4.循环递增数组中求最小的key
public static int findMin(int[] num, int begin, int end) {
//当数组只有一个元素时,num[begin] == num[end] 直接返回
//当数组第一位小于最后一位时,第一位即为最小,因为数组循环递增
if (num[begin] <= num[end]) {
return num[begin];
}
//数组只有两位时,例如2,1这时候直接范围最后一位,如果是1,2则上边的If语句已经做出判断,这里不用考虑
if (end - begin == 1) {
return num[end];
}
//算出中间位
int middle = (begin + end) / 2;
//如果中间位小于左边的,那么中间位便是最小值,因为数组循环递增
if (num[middle] < num[middle - 1]) {
return num[middle];
}
//当中间位小于第一位时,中间位左边为严格递增,右边为循环递增,最小值一定在循环递增处
//反之,左边为循环递增,最小值一定在循环递增处
if (num[middle] > num[begin]) {
return findMin(num, middle + 1, end);
} else {
return findMin(num, begin, middle - 1);
}
}
4.求两个递增数组中第k大的值
public static int findKth(int a[],int astart,int alength,int b[],int bstart,int blength,int k ){
if (alength>blength){
return findKth(b,bstart,blength,a,astart,alength,k);
}
if (alength==0){
return b[bstart+k-1];
}
if (k==1){
return Math.min(a[astart],b[bstart]);
}
int pa=Math.min(k/2,alength);
int qa=k-pa;
if (a[astart+pa-1]<b[bstart+qa-1]){
return findKth(a,astart+pa,alength-pa,b,bstart,blength,k-pa);
}else if (a[astart+pa-1]>b[bstart+qa-1]){
return findKth(a,astart,alength,b,bstart+qa,blength-qa,k-qa);
}else {
return a[astart+pa-1];
}
}
二、简单dp
1.连续数组的最大和
public static int getMax(int arr[]){
int max=arr[0];
int cur=arr[0];
for (int i=1;i<arr.length;i++){
cur=Math.max(arr[i],arr[i]+cur);
if (cur>max){
max=cur;
}
}
return max;
}
2.股票的最佳买入卖出时机
public static int getMax(int arr[]){
int maxCur=0;
int max=0;
for (int i=1;i<arr.length;i++){
maxCur=Math.max(0,maxCur+=arr[i]-arr[i-1]);
max=Math.max(max,maxCur);
}
return max;
}
public static int maxProfit(int prices[]) {
int minprice = Integer.MAX_VALUE;
int maxprofit = 0;
for (int i = 0; i < prices.length; i++) {
if (prices[i] < minprice)
minprice = prices[i];
else if (prices[i] - minprice > maxprofit)
maxprofit = prices[i] - minprice;
}
return maxprofit;
}
/**
* description:假设你有一个数组,它的第i个元素是一支给定的股票在第i天的价格。设计一个算法来找到最大的利润。你最多可以完成两笔交易。
* question_name:买卖股票的最佳时机 III
* **/
public static int maxProfit3(int[] prices,int day)
{
if(prices.length<2)
return 0;
if(prices.length==2)
return Math.max(0,prices[1]-prices[0]);
int max_profile=0;
for(int i=2;i<=prices.length-1;i++)
{
max_profile=Math.max(getMaxProfile(prices,0,i-1)+getMaxProfile(prices,i-1,prices.length-1),max_profile);
}
return max_profile;
}
/**
* description:得到一次交易的最大利润
* parameter:
* prices:每天的价格
* begain_index:起始角标
* end_index:结束角标
*/
public static int getMaxProfile(int [] prices , int begain_index, int end_index)
{
int min_price=prices[begain_index];
int max_profile=Integer.MIN_VALUE;
for(int i=begain_index;i<=end_index;i++)
{
min_price=Math.min(min_price,prices[i]);
max_profile=Math.max(max_profile,prices[i]-min_price);
}
return max_profile;
}
//dp解法
public static int getMaxMoney(int arr[]){
int max=0;
int cur=0;
for (int i=0;i<arr.length-1;i++){
cur=Math.max(cur+arr[i+1]-arr[i],arr[i+1]-arr[i]);
max=Math.max(cur,max);
}
return max;
}
三、栈
对一个栈进行排序(递归和非递归)
//递归解法
public static void main(String[] args) {
Stack<Integer>stack=new Stack<>();
stack.push(1);
stack.push(9);
stack.push(6);
stack.push(4);
sortStack(stack);
while (!stack.isEmpty()){
System.out.println(stack.pop());
}
}
//插入操作
public static Stack<Integer> insert(Stack<Integer>stack,int v){
if (stack.isEmpty()||v<=stack.peek()){
//在stack为空或者插入的值小于等于栈顶时直接进行插入操作
stack.push(v);
//返回
return stack;
}
//取出栈顶元素
int temp=stack.pop();
//插入待插入的值
insert(stack,v);
//插入栈顶元素
stack.push(temp);
//返回
return stack;
}
//执行排序操作
public static Stack<Integer>sortStack(Stack<Integer> stack){
if (stack.isEmpty()){
//如果栈是空的则直接返回
return stack;
}
//将栈顶元素出栈
int temp=stack.pop();
//进行排序
stack=sortStack(stack);
//将栈顶元素入栈
stack=insert(stack,temp);
//返回
return stack;
}
}
```cpp
//非递归解法用另外一个栈进行解决,比较简单
四、树
主要考察树的遍历
1.树的层序遍历 (代码较为简单,略)
2.之字形打印二叉树
```cpp
public static void printTree(TreeNode root){
if (root==null){
return;
}
Stack<TreeNode>stack1=new Stack<>();
Stack<TreeNode>stack2=new Stack<>();
stack1.push(root);
int level=1;
while (!stack1.isEmpty()||!stack2.isEmpty()){
if (level%2==1){
while (!stack1.isEmpty()){
TreeNode cur=stack1.pop();
System.out.print(cur.val+" ");
if (cur.left!=null){
stack2.push(cur.left);
}
if (cur.right!=null){
stack2.push(cur.right);
}
}
}else {
while (!stack2.isEmpty()){
TreeNode cur=stack2.pop();
System.out.print(cur.val+" ");
if (cur.right!=null){
stack1.push(cur.right);
}
if (cur.left!=null){
stack1.push(cur.left);
}
}
}
System.out.println();
level++;
}
}```
3.把一个BST转换成双向链表
```cpp
public static TreeNode convert2List(TreeNode pRootOfTree) {
if (pRootOfTree == null) {
return null;
}
TreeNode list = null;
Stack<TreeNode> stack = new Stack<>();
while (pRootOfTree != null || !stack.isEmpty()) {
if (pRootOfTree != null) {
stack.push(pRootOfTree);
pRootOfTree = pRootOfTree.right;
} else {
pRootOfTree = stack.pop();
if (list == null) {
list = pRootOfTree;
} else {
list.left = pRootOfTree;
pRootOfTree.right = list;
list = pRootOfTree;
}
pRootOfTree = pRootOfTree.left;
}
}
return list;
}```
4.二叉树的最近公共祖先
```c
在这里插入代码片
5.是否是平衡二叉树的高效判断方法
public class BalanceTree {
private static class HeightHolder{
int height;
}
public static void main(String[] args) {
LinkedList<Integer>linkedList=new LinkedList<>();
linkedList.add(1);
linkedList.addFirst(2);
linkedList.remove(1);
}
public static boolean isBalanceTree(TreeNode root,HeightHolder heightHolder){
if (root==null){
heightHolder.height=0;
return true;
}
HeightHolder leftHolder=new HeightHolder();
HeightHolder rightHolder=new HeightHolder();
if (isBalanceTree(root.left,leftHolder)&&isBalanceTree(root.right,rightHolder)){
int diff=leftHolder.height-rightHolder.height;
if (Math.abs(diff)<=1){
heightHolder.height=1+(rightHolder.height>leftHolder.height?rightHolder.height:leftHolder.height);
return true;
}
}
return false;
}
}
6.BST的后继节点
public class FindSuccessor {
public static void main(String[] args) {
TreeNode root = new TreeNode(15);
root.left = new TreeNode(10);
root.left.parent=root;
root.left.left = new TreeNode(6);
root.left.left.parent=root.left;
root.left.right = new TreeNode(12);
root.left.right.parent=root.left;
root.right = new TreeNode(20);
root.right.parent=root;
root.right.left = new TreeNode(16);
root.right.left.parent=root.right;
System.out.println(findSuccessor(root, 15));
}
//这一种方法可以找到无论是带父节点指针还是没有父节点指针都可以找到
public static int findSuccessor(TreeNode root, int key) {
if (root == null) {
return -1;
}
Stack<TreeNode> stack = new Stack<>();
boolean isFound = false;
TreeNode cur = root;
int result = -1;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
if (!stack.isEmpty()) {
cur = stack.pop();
if (isFound) {
System.out.println();
return cur.val;
}
if (cur.val == key) {
isFound = true;
}
}
cur = cur.right;
}
System.out.println();
return result;
}
//如果有父节点
public static TreeNode findSuccessor1(TreeNode node) {
if (node==null){
return null;
}
TreeNode pNext=null;
if (node.right!=null){
TreeNode pRight=node.right;
while (pRight.left!=null){
pRight=pRight.left;
pNext=pRight;
}
}else if (node.parent!=null){
TreeNode pcur=node;
TreeNode parent=node.parent;
while (parent!=null&&pcur==parent.right){
pcur=parent;
parent=parent.parent;
}
pNext=parent;
}
return pNext;
}
}```
6.二叉树中一个节点的所有祖先节点
```c
public static void searchNode(TreeNode root, TreeNode node) {
if (root == null || node == null) return;
Stack<TreeNode> s = new Stack<>();
TreeNode p = root;
TreeNode pre = null; //上一次出栈的结点
while (p != null || !s.isEmpty()) {
while (p != null) {
//这个while循环的思想还是一直往左找,找的过程结点入栈,如果找到了就打印输出并返回。
s.push(p);
if (p.value == node.value) {
for (TreeNode treeNode : s) {
System.out.print(treeNode.value + " ");
}
return;
}
p = p.left;
}
//走到这一步说明栈顶元素的左子树为null,那么就开始往栈顶元素的右子树上去找。
if (!s.isEmpty()) {
p = s.peek();
//如果栈顶元素的右子树为null,或者右子树被遍历过,则弹栈。
while (p.right == null || pre != null && p.right == pre) {
pre = s.pop();
p = s.peek();
}
//继续遍历p的右子树
p = p.right;
}
}
}```
四、链表
1.反转链表
```c
public static Node reverse(Node head){
if (head==null){
return null;
}
Node cur=null;
Node pre=null;
Node newHead=head;
cur=head;
while(cur!=null){
Node pnext=cur.next;
cur.next=pre;
pre=cur;
if (pnext==null){
newHead=cur;
return newHead;
}
cur=pnext;
}
return newHead;
}```
2.循环链表的各种操作
```c
public class CircleList {
private static class Node{
int value;
Node next;
public Node(int value){
this.value=value;
}
}
public static void main(String[] args) {
Node head=new Node(1);
head.next=new Node(2);
head.next.next=new Node(3);
Node p= head.next.next.next=new Node(4);
head.next.next.next.next=new Node(5);
head.next.next.next.next.next=new Node(6);
head.next.next.next.next.next.next=new Node(7);
head.next.next.next.next.next.next.next=new Node(8);
Node q= head.next.next.next.next.next.next.next.next=new Node(9);
q.next=p;
System.out.println(exitLoop(head));
System.out.println(findStart(head).value);
System.out.println(countOfCircle(head));
System.out.print(lengthOfList(head));
}
//判断链表中是否存在环
public static boolean exitLoop(Node head){
Node slow=head;
Node fast=head;
while (slow!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if (slow==fast){
return true;
}
}
return false;
}
//找出链表的入口节点
public static Node findStart(Node head){
Node slow=head;
Node fast=head;
while (slow!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if (slow==fast)
break;
}
if (slow==null||fast.next==null)
return null;
slow=head;
while (slow!=fast){
slow=slow.next;
fast=fast.next;
}
return slow;
}
//计算环中节点的个数
public static int countOfCircle(Node head){
Node slow=head;
Node fast=head;
while (slow!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if (slow==fast)
break;
}
if (slow==null||fast.next==null)
return 0;
int count=1;
while (slow!=fast.next){
slow=slow.next;
fast=fast.next.next;
count++;
}
return count;
}
//求链表的长度
public static int lengthOfList(Node head){
int circle=countOfCircle(head);
Node start=findStart(head);
Node slow=head;
int length=0;
while (slow!=start){
slow=slow.next;
length++;
}
return length+=circle;
}
}```
五、线程
1.循环打印数字
```c
/***
*
*
* 三个线程循环打印数字,第一个打印12345,第二个打印678910 以此类推直到打印75
*
* **/
public class NumberPrinter {
private static AtomicInteger atomicInteger=new AtomicInteger(1);
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock=new ReentrantLock();
Condition conditionA=lock.newCondition();
Condition conditionB=lock.newCondition();
Condition conditionC=lock.newCondition();
ThreadGroup threadGroup=new ThreadGroup("xxx");
Thread threadA=new Thread(threadGroup,new PrinterTask(lock,conditionA,conditionB));
Thread threadB=new Thread(threadGroup,new PrinterTask(lock,conditionB,conditionC));
Thread threadC=new Thread(threadGroup,new PrinterTask(lock,conditionC,conditionA));
threadA.start();
Thread.sleep(1000);
threadB.start();
Thread.sleep(1000);
threadC.start();
}
private static class PrinterTask implements Runnable{
private ReentrantLock lock;
private Condition myCondition;
private Condition nextConditon;
private static final int TOTAL_PRINT_COUNT=5;
private static final int PER_PRINT_COUNT=5;
public PrinterTask(ReentrantLock lock,Condition myCondition,Condition nextConditon){
this.lock=lock;
this.nextConditon=nextConditon;
this.myCondition=myCondition;
}
@Override
public void run() {
lock.lock();
try {
for (int i=0;i<TOTAL_PRINT_COUNT;i++){
System.out.println(Thread.currentThread().getName()+"打印的");
for (int j=0;j<PER_PRINT_COUNT;j++){
System.out.print(atomicInteger.getAndIncrement()+" ");
}
System.out.println();
//通过afterCondition通知后面线程
nextConditon.signalAll();
if(i<=TOTAL_PRINT_COUNT-1){
try {
//本线程释放锁并等待唤醒
myCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}finally {
lock.unlock();
}
}
}
}