文章目录
计数排序,基数排序,桶排序,选择排序,冒泡排序,插入排序,希尔排序,归并排序,快速排序
的基础算法思想和代码
二分法
时间复杂度O(logN)
针对于有序数组:当数据量很大适宜采用该方法。采用二分法查找时,数据需是排好序的。
基本思想:
-
假设数据是按升序排序的,对于给定值key,从序列的中间位置k开始比较,
-
如果当前位置arr[k]值等于key,则查找成功;
-
若key小于当前位置值arr[k],则在数列的前半段中查找,arr[low,mid-1];前半段继续从中间位置比较;
-
若key大于当前位置值arr[k],则在数列的后半段中继续查找arr[mid+1,high],
-
直到找到为止,时间复杂度:O(log(n))
…
- 在一个有序数组中,查找某个数是否存在
public class Exist {
public static void main(String[] args) {
int arr[] = {1,2,3,5,6,9};
System.out.println(exist(arr, 9));
}
public static boolean exist(int[] sortArr,int num){
if (sortArr == null && sortArr.length == 0){
return false;
}
int L = 0;
int R = sortArr.length - 1;
int mid = 0;
while (L<R){
//下面语句相当于mid = (L+R)/2
mid = L + ((R - L) >> 1);
if (sortArr[mid] == num){
return true;
}else if(sortArr[mid] > num){
R = mid - 1;
}else {
L = mid + 1;
}
}
return sortArr[L] == num;
}
}
- 求大于某个数所在位置的索引(如果相同,取最左边的数)
/**
* @Author ZhouT
* @Date 2021/3/23 18:51
* 求大于某个数所在位置的索引(如果相同,取最左边的数)
*/
public class NearNumIndex {
public static void main(String[] args) {
int[] arr = {1,5,6,9,8,10,99};
int index = nearNumIndex(arr, 9);
System.out.println(index);
}
protected static int nearNumIndex(int[] arr, int value) {
int L = 0;
int R = arr.length - 1;
int index = -1;
while (L <= R) {
int mid = L + ((R - L) >> 1);
if (arr[mid] >= value) {
index = mid;
R = mid - 1;
} else {
L = mid + 1;
}
}
return index;
}
}
异或
1.交换两个数
public class Swap {
public static void main(String[] args) {
int[] arr = {1,2};
swap1(arr ,arr[1],arr[0]);
System.out.println( Arrays.toString(arr));
}
//第一种方法:
static void swap1(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
//第二种方法
static void swap2(int[] arr, int i, int minPos) {
int temp = arr[i];
arr[i] = arr[minPos];
arr[minPos] = temp;
}
}
2.一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这个奇数次并打印这个数
public class SelectNum {
public static void printOddTimesNum(int[] arr){
int eor = 0;
for (int i = 0; i < arr.length; i++) {
eor ^= arr[i];
}
System.out.println(eor);
}
}
链表
1.单向链表的反转
/**
* 单链表
*/
public static class Node{
public int value;
public Node next;
public Node(int data){
value = data;
}
}
public static 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;
}
2.双向链表的反转
/**
* 双向链表
*/
public static class DoubleNode{
public int value;
public DoubleNode last;
public DoubleNode next;
public DoubleNode(int data){
value = data;
}
}
public static DoubleNode reverseDoubleList(DoubleNode head){
DoubleNode pre = null;
DoubleNode next = null;
while (head!=null){
next = head.next;
head.next = pre;
head.last = next;
pre = head;
head = next;
}
return pre;
}
3.删除单向链表中的某个数
/**
* @Author Z
* @Date 2021/3/24 13:35
* 删除链表中的某个数(可能有多个相同的数)
*/
public class DeleteNumList {
public static class Node {
public int value;
public Node next;
public Node(int data) {
value = data;
}
}
public static Node removeValue(Node head, int num) {
//处理如果链表头是要删除的数
while (head != null) {
if (head.value != num) {
break;
}
head = head.next;
}
Node pre = head;
Node cur = head;
while (cur != null) {
if (cur.value == num) {
pre.next = cur.next;
} else {
pre = cur;
}
cur = cur.next;
}
return head;
}
}
栈和队列
时间复杂度O(1)
队列先进先出
栈先进后出
1.使用双向链表实现栈和队列
/**
* @Author ZhouT
* @Date 2021/3/24 14:33
* 使用双向链表实现栈和队列
*/
public class DoubleEndsQueueToStackAndQueue {
public static class Node<T> {
public T value;
public Node<T> last;
public Node<T> next;
public Node(T data) {
value = data;
}
}
public static class DoubleEndsQueue<T> {
public Node<T> head;
public Node<T> tail;
//从头部加东西
public void addFromHead(T value) {
Node<T> cur = new Node<T>(value);
if (head == null) {
head = cur;
tail = cur;
} else {
cur.next = head;
head.last = cur;
head = cur;
}
}
//从尾部加东西
public void addFromBottom(T value) {
Node<T> cur = new Node<T>(value);
if (head == null) {
head = cur;
tail = cur;
} else {
cur.last = tail;
tail.next = cur;
tail = cur;
}
}
//从头部弹出
public T popFromHead() {
if (head == null) {
return null;
}
Node<T> cur = head;
if (head == tail) {
head = null;
tail = null;
} else {
head = head.next;
cur.next = null;
head.last = null;
}
return cur.value;
}
//从尾部弹出
public T popFromBottom() {
if (head == null) {
return null;
}
Node<T> cur = tail;
if (head == tail) {
head = null;
tail = null;
} else {
tail = tail.last;
tail.next = null;
cur.last = null;
}
return cur.value;
}
public boolean isEmpty() {
return head == null;
}
}
//栈
public static class MyStack<T> {
private DoubleEndsQueue<T> queue;
public MyStack() {
queue = new DoubleEndsQueue<T>();
}
public void push(T value) {
queue.addFromHead(value);
}
public T pop() {
return queue.popFromHead();
}
public boolean isEmpty() {
return queue.isEmpty();
}
}
//队列
public static class MyQueue<T> {
private DoubleEndsQueue<T> queue;
public MyQueue() {
queue = new DoubleEndsQueue<T>();
}
public void push(T value) {
queue.addFromHead(value);
}
public T poll() {
return queue.popFromBottom();
}
public boolean isEmpty() {
return queue.isEmpty();
}
}
public static boolean isEqual(Integer o1, Integer o2) {
if (o1 == null && o2 != null) {
return false;
}
if (o1 != null && o2 == null) {
return false;
}
if (o1 == null && o2 == null) {
return true;
}
return o1.equals(o2);
}
public static void main(String[] args) {
int oneTestDataNum = 100;
int value = 10000;
int testTimes = 100000;
for (int i = 0; i < testTimes; i++) {
MyStack<Integer> myStack = new MyStack<>();
MyQueue<Integer> myQueue = new MyQueue<>();
Stack<Integer> stack = new Stack<>();
Queue<Integer> queue = new LinkedList<>();
for (int j = 0; j < oneTestDataNum; j++) {
int nums = (int) (Math.random() * value);
if (stack.isEmpty()) {
myStack.push(nums);
stack.push(nums);
} else {
if (Math.random() < 0.5) {
myStack.push(nums);
stack.push(nums);
} else {
if (!isEqual(myStack.pop(), stack.pop())) {
System.out.println("oops!");
}
}
}
int numq = (int) (Math.random() * value);
if (stack.isEmpty()) {
myQueue.push(numq);
queue.offer(numq);
} else {
if (Math.random() < 0.5) {
myQueue.push(numq);
queue.offer(numq);
} else {
if (!isEqual(myQueue.poll(), queue.poll())) {
System.out.println("oops!");
}
}
}
}
}
System.out.println("finish!");
}
}
2.使用数组实现栈和队列
即怎么用数组实现不超过固定大小的队列和栈?
栈:一个栈,使用数组,加东西使index++,弹出东西使index–
队列:
令pollIndex=0;pushIndex=0;
增加一个变量size = 0;
limit为这个队列的最大限制;
如下:
public static class MyQueue {
private int[] arr;
private int pushi;
private int polli;
private int size;
private final int limit;
public MyQueue(int l) {
arr = new int[l];
pushi = 0;
polli = 0;
size = 0;
limit = l;
}
public void push(int value) {
if (size == limit) {
throw new RuntimeException("栈满了,不能再加了");
}
size++;
arr[pushi] = value;
pushi = nextIndex(pushi);
}
public int pop() {
if (size == 0) {
throw new RuntimeException("栈空了,不能再拿了");
}
size--;
int ans = arr[polli];
polli = nextIndex(pulli);
return ans;
}
public boolean isEmpty() {
return size == 0;
}
private int nextIndex(int i) {
return i < limit - 1 ? i + 1 : 0;
}
}
3.实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能
方法1:
思想:时间复杂度O(1)
-
准备两个栈Data栈和Min栈,就可以实现了
-
第一次push时把这个数同时push进Data栈和Min栈;
-
把第二次push的数放入Data栈,和Min栈的栈顶元素进行比较,
-
如果这个数比Min栈栈顶元素大,则把Min栈栈顶元素push到Min栈中(这时,Min栈有两个相同的数);
-
如果这个数比Min栈栈顶元素小,则把这个数push到Min栈中,这个数就成为了新的栈顶;
-
总而言之,每次push时,就是把这个数push到Data数组中,同时和Min栈的栈顶元素进行比较,如果这个数比Min栈栈顶元素小,则把这个数push进去,如果栈顶元素更小,把栈顶元素再次push进去;
-
当push完成后,Min栈栈顶元素就是最小的数了。
-
当发生pop操作,把Data栈和Min栈的栈顶元素同时弹出。
-
如何实现getMin呢?就是弹出Min栈的栈顶元素。
//算法思想:
public void push(int newNum) {
//如果当前栈是空的,直接放进去
if (this.stackMin.isEmpty()) {
this.stackMin.push(newNum);
} else if (newNum < this.getmin()) {
//如果当前数比栈顶元素小,则把这个数放进去
this.stackMin.push(newNum);
} else {
//如果当前数比栈顶元素大,则把这个栈顶元素再次放入
int newMin = this.stackMin.peek();
this.stackMin.push(newMin);
}
/**
* @Author Z
* @Date 2021/3/24 16:45
* 实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能
*/
public class GetMinStack {
public static class MyStack1 {
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
public MyStack1() {
this.stackData = new Stack<Integer>();
this.stackMin = new Stack<Integer>();
}
public void push(int newNum) {
if (this.stackMin.isEmpty()) {
this.stackMin.push(newNum);
} else if (newNum <= this.getmin()) {
this.stackMin.push(newNum);
}
this.stackData.push(newNum);
}
public int pop() {
if (this.stackData.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
int value = this.stackData.pop();
if (value == this.getmin()) {
this.stackMin.pop();
}
return value;
}
public int getmin() {
if (this.stackMin.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
return this.stackMin.peek();
}
}
public static class MyStack2 {
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
public MyStack2() {
this.stackData = new Stack<Integer>();
this.stackMin = new Stack<Integer>();
}
public void push(int newNum) {
//如果当前栈是空的,直接放进去
if (this.stackMin.isEmpty()) {
this.stackMin.push(newNum);
} else if (newNum < this.getmin()) {
//如果当前数比栈顶元素小,则把这个数放进去
this.stackMin.push(newNum);
} else {
//如果当前数比栈顶元素大,则把这个栈顶元素再次放入
int newMin = this.stackMin.peek();
this.stackMin.push(newMin);
}
this.stackData.push(newNum);
}
public int pop() {
if (this.stackData.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
this.stackMin.pop();
return this.stackData.pop();
}
public int getmin() {
if (this.stackMin.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
return this.stackMin.peek();
}
}
public static void main(String[] args) {
MyStack1 stack1 = new MyStack1();
stack1.push(3);
System.out.println(stack1.getmin());
stack1.push(4);
System.out.println(stack1.getmin());
stack1.push(1);
System.out.println(stack1.getmin());
System.out.println(stack1.pop());
System.out.println(stack1.getmin());
System.out.println("=============");
MyStack1 stack2 = new MyStack1();
stack2.push(3);
System.out.println(stack2.getmin());
stack2.push(4);
System.out.println(stack2.getmin());
stack2.push(1);
System.out.println(stack2.getmin());
System.out.println(stack2.pop());
System.out.println(stack2.getmin());
}
}
方法2:(省空间浪费时间,推荐1,并无本质区别)
跟1相似,
-
第一次push时把这个数同时push进Data栈和Min栈;
-
把第二次push的数放入Data栈,和Min栈的栈顶元素进行比较,
-
如果这个数比Min栈栈顶元素大,~~则把Min栈栈顶元素push到Min栈中(这时,Min栈有两个相同的数);~~在方法2,栈顶元素更小,则不进行操作了
-
如果这个数比Min栈栈顶元素小于等于,则把这个数push到Min栈中,这个数就成为了新的栈顶;
-
总而言之,每次push时,就是把这个数push到Data数组中,同时和Min栈的栈顶元素进行比较,如果这个数比Min栈栈顶元素小于等于,则把这个数push进去,如果栈顶元素更小,
把栈顶元素再次push进去,不用进行操作了; -
当push完成后,Min栈栈顶元素就是最小的数了。
-
当发生pop操作,把Data栈和Min栈的栈顶元素同时弹出。
-
方法2新增:pop时,方法1时Min栈和Data栈同时弹出,而2中,还要再进行比较,如果Data栈和Min栈的栈顶元素不相同,则只弹出Data栈的数;如果Data栈和Min栈的栈顶元素相同,则同时弹出Data栈和Min栈中的数;
-
如何实现getMin呢?就是弹出Min栈的栈顶元素。
4.如何用栈结构实现队列结构
搞两个栈。
如给1,2,3,4,5,放进push栈后,依次弹出到pop栈
然后再将pop栈弹出,就实现了队列结构。
注意:用时保证pop栈是空的,push弹出时要全部弹出放入到pop栈中
/**
* @Author Z
* @Date 2021/3/24 17:36
* 如何用栈结构实现队列结构
*/
public class TwoStacksImplementQueue {
public static class TwoStacksQueue {
public Stack<Integer> stackPush;
public Stack<Integer> stackPop;
public TwoStacksQueue() {
stackPush = new Stack<Integer>();
stackPop = new Stack<Integer>();
}
// push栈向pop栈倒入数据
private void pushToPop() {
if (stackPop.empty()) {
while (!stackPush.empty()) {
stackPop.push(stackPush.pop());
}
}
}
public void add(int pushInt) {
stackPush.push(pushInt);
pushToPop();
}
public int poll() {
if (stackPop.empty() && stackPush.empty()) {
throw new RuntimeException("Queue is empty!");
}
pushToPop();
return stackPop.pop();
}
public int peek() {
if (stackPop.empty() && stackPush.empty()) {
throw new RuntimeException("Queue is empty!");
}
pushToPop();
return stackPop.peek();
}
}
public static void main(String[] args) {
TwoStacksQueue test = new TwoStacksQueue();
test.add(1);
test.add(2);
test.add(3);
System.out.println(test.peek());
System.out.println(test.poll());
System.out.println(test.peek());
System.out.println(test.poll());
System.out.println(test.peek());
System.out.println(test.poll());
}
}
5.如何用队列结构实现栈结构
弱智问题,但是思想上比较难想到
-
搞两个队列,如原Data队列和辅助Help队列。
-
如1,2,3,4,5;依次进入Data队列中
-
如果要弹出的话,要表现的像一个栈,即弹出5,4,3,2,1;
-
Data队列现在是5,4,3,2,1;现在把其中1,2,3,4弹出到Help队列中
把Data中的5弹出,此时Data队列里面数已经没了 -
把Help队列当成Data队列,把Help中3,2,1弹出到Data队列中
然后把4弹出,此时Help队列里面数已经没了 -
依次这样来回,就可以实现这个功能了
/**
* @Author Z
* @Date 2021/3/24 17:39
* 如何用队列结构实现栈结构
*/
public class TwoQueueImplementStack {
public static class TwoQueueStack<T> {
public Queue<T> queue;
public Queue<T> help;
public TwoQueueStack() {
queue = new LinkedList<>();
help = new LinkedList<>();
}
public void push(T value) {
queue.offer(value);
}
public T poll() {
while (queue.size() > 1) {
help.offer(queue.poll());
}
T ans = queue.poll();
Queue<T> tmp = queue;
queue = help;
help = tmp;
return ans;
}
public T peek() {
while (queue.size() > 1) {
help.offer(queue.poll());
}
T ans = queue.peek();
Queue<T> tmp = queue;
queue = help;
help = tmp;
help.offer(ans);
return ans;
}
public boolean isEmpty() {
return queue.isEmpty();
}
}
}
6.可以发现图的宽度优先遍历都是经过队列实现的,图的深度优先遍历都是经过栈实现的
问:如何通过栈实现图的宽度优先遍历,通过队列实现图的深度优先遍历
通过第4和第5题,将栈用队列实现,将队列用栈实现就可以了
递归
求数组arr[L…R]中的最大值,怎么用递归方法实现。
1)将[L…R]范围分成左右两半。左:[L…Mid] 右[Mid+1…R]
2)左部分求最大值,右部分求最大值
3) [L…R]范围上的最大值,是max{左部分最大值,右部分最大值
public class GetMax {
// 求arr中的最大值
public static int getMax(int[] arr) {
return process(arr, 0, arr.length - 1);
}
// arr[L..R]范围上求最大值
public static int process(int[] arr, int L, int R) {
if (L == R) { // arr[L..R]范围上只有一个数,直接返回,base case
return arr[L];
}
// L..mid mid+1...R
// int mid = (L+R)/2
int mid = L + ((R - L) >> 1); // 中点
int leftMax = process(arr, L, mid);
int rightMax = process(arr, mid + 1, R);
return Math.max(leftMax, rightMax);
}
}
哈希表HashMap,HashSet
无序的
时间复杂度O(1)
-
哈希表在使用层面上可以理解为一种集合结构
-
如果只有key,没有伴随数据value,可以使用HashSet结构
-
如果既有key,又有伴随数据value,可以使用HashMap结构
-
有无伴随数据,是HashMap和HashSet唯一的区别,实际结构是一回事
-
使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为O(1),但是常数时间比较大
-
放入哈希表的东西,如果是基础类型,内部按值传递,内存占用是这个东西的大小
-
放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用是8字节
map.put(key,value)
有序表TreeMap,TreeSet
时间复杂度O(logN)
有序的
-
有序表在使用层面上可以理解为一种集合结构
-
如果只有key,没有伴随数据value,可以使用TreeSet结构
-
如果既有key,又有伴随数据value,可以使用TreeMap结构
-
有无伴随数据,是TreeSet和TreeMap唯一的区别,底层的实际结构是一回事
-
有序表把key按照顺序组织起来,而哈希表完全不组织
-
红黑树、AVL树、size-balance-tree和跳表等都属于有序表结构,只是底层 具体实现不同
-
放入如果是基础类型,内部按值传递,内存占用就是这个东西的大小
-
放入如果不是基础类型,内部按引用传递,内存占用是8字节
-
不管是什么底层具体实现,只要是有序表,都有以下固定的基本功能和固定 的时间复杂度
-
void put(K key, V value) 将一个(key,value)记录加入到表中,或者将key的记录 更新成value。
-
V get(K key) 根据给定的key,查询value并返回。
-
void remove(K key) 移除key的记录。
-
boolean containsKey(K key) 询问是否有关于key的记录。