一 链表
1.链表结构
(1)单向链表节点结构
public class Node{
private int value;
private Node next;
public Node(int data){
}
}
(2)双向链表节点结构
public class DoubleNode {
private int data;
private DoubleNode last;
private DoubleNode next;
public DoubleNode(int data) {
}
}
- 链表反转
(1)单链表反转
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;
}
(2)双链表反转
public DoubleNode reverseDoubleNode(DoubleNode head){
DoubleNode pre = null;
DoubleNode next = null;
while (head!=null){
next = head.next;
head.next = pre;
head.pre = next;
pre = head;
head = next;
}
return pre;
}
- 删除给定的值
思路:先看头部是否需要删掉,先遍历链表,找到第一个不满足要求的头部。将此头部作为最终的头部。接着遍历链表,去除满足要求的节点。最后返回头部节点。
public Node removeSpecialNode(Node head, int key) {
while (head != null) {
if (head.value != key) {
break;
} else {
head = head.next;
}
}
Node pre = head;
Node cur = head;
while (cur!=null){
if(cur.value==key){
pre.next=cur.next;
}else{
pre = cur;
}
cur = cur.next;
}
return head;
}
二 栈和队列
- 栈
数据先进后出
2.队列
数据先进先出
2.链表实现栈和队列
(1)双向链表实现栈
头插法,每次新增数据,就在头部加一个节点。从头进,从头出。
(2)双向链表实现队列
从头部插入,尾部去除,有个tail指针,永远指向尾部
3.数组实现栈和队列
3.1 固定长度数组实现栈
有一个index,放完数据index++,减少数据就取–index的数据
3.2 数组实现队列
环形数组
两个指针,一个是尾指针,指向在队列里存在时间最长的数据的index。一个头指针,指向新加入的数据的index
public MyQueue(int limit) {
this.arr = new int[limit];
this.push = 0;
this.pop = 0;
this.limit = limit;
this.size = 0;
}
public void push(int value) {
if (size == limit) {
throw new RuntimeException("The Queue is full.");
}
arr[push] = value;
size++;
push = (push + 1) % limit;
}
public int pop() {
if (size == 0) {
throw new RuntimeException("The Queue is empty.");
}
int value = arr[pop];
size--;
pop = (pop + 1) % limit;
return value;
}
- 常见面试题
4.1 怎么使用数组实现不超过固定大小的栈和队列?
栈:正常使用
队列:循环数组
4.2 实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能
(1) pop, push, getMin操作的时间复杂度都是O(1)
(2) 设计的栈类型可以使用现成的栈结构
注意:返回栈中最小元素,是指无论栈中有多少个元素,都要返回最小的
public class MyStack {
private Stack<Integer> dataSatck;
private Stack<Integer> minStack;
public MyStack(){
this.dataSatck = new Stack<>();
this.minStack = new Stack<>();
}
public void push(int value){
if(minStack.empty() || value<minStack.peek()){
minStack.push(value);
}else{
minStack.push(minStack.peek());
}
dataSatck.push(value);
}
public int pop(){
if(dataSatck.empty()){
throw new RuntimeException("The stack is empty. Can't pop data");
}
minStack.pop();
return dataSatck.pop();
}
public int getMinNumber(){
if(minStack.empty()){
throw new RuntimeException("The stack is empty. Don't has min number.");
}
return minStack.peek();
}
}
(3) 如何用栈实现队列结构?
pop栈为空,才能将push栈导入pop, push栈导入到pop栈时,必须一次性倒完。
public class TwoStackToQueue {
private Stack<Integer> push;
private Stack<Integer> pop;
public TwoStackToQueue(){
this.pop = new Stack<>();
this.push = new Stack<>();
}
public void push(int value){
push.push(value);
}
public int pop(){
if(pop.empty()){
while (!push.empty()){
pop.push(push.pop());
}
return pop.pop();
}else {
return pop.pop();
}
}
}
(4)如何用队列实现栈结构?
使用两个队列,倒着来,每次留下最后一个数,作为返回值。
假设有两个队列,data与help。此时data里已经按顺序进入了1,2,3,4,5五个数字。第一轮,将1,2,3,4依次出队列进入help队列,5弹出。第二轮help里的1,2,3依次进入data队列,4弹出。
public class TwoQueueToStack {
private Queue<Integer> queue1;
private Queue<Integer> queue2;
private boolean isQueue1;
public TwoQueueToStack(){
this.queue1 = new LinkedList<>();
this.queue2 = new LinkedList<>();
isQueue1 = true;
}
public void push(int value){
if(isQueue1){
queue1.add(value);
}else{
queue2.add(value);
}
}
public int pop() {
int result;
if (isQueue1) {
if (queue1.isEmpty()) {
throw new RuntimeException("The stack is empty, can't pop data");
}
result = queue1.poll();
while (!queue1.isEmpty()) {
queue2.add(result);
result = queue1.poll();
}
isQueue1 = false;
} else {
if (queue2.isEmpty()) {
throw new RuntimeException("The stack is empty, can't pop data");
}
result = queue2.poll();
while (!queue2.isEmpty()) {
queue1.add(result);
result = queue2.poll();
}
isQueue1 = true;
}
return result;
}
}
三 递归
示例:用递归方法求一个数组arr[L…R]中的最大值
(1) 将[L…R]范围分为左右两半。左:[L…Mid],右[mid+1…R]
(2) 左部分求最大值,右部分求最大值
(3) [L…R]范围上的最大值,是max{左部分最大值,右部分最大值}
注意: (2) 是个递归过程,当范围上只有一个数,就可以不用再递归了
public static void main(String[] args) {
int[] arr = new int[]{2,4,7,5,8,3};
int value = findMaxNumber(arr,0,arr.length-1);
System.out.println(value);
}
private static int findMaxNumber(int[] arr, int left, int right){
if(left==right){
return arr[left];
}else{
int mid = left+(right-left)/2;
int leftMax = findMaxNumber(arr,0,mid);
int rightMax = findMaxNumber(arr,mid+1,right);
return Math.max(leftMax,rightMax);
}
}
T(N) = a*T(N/b)+O(N^d)
数据量为N,b为子问题递归的规模,因此每次都把数据集分为两半,所以b=2。子问题规模一样,调用了子问题a次,如代码中,调用了两次findMaxNumber,所以a=2。子问题其他复杂度为d,比如输出值时,时间复杂度为O(N), 所以d=1。
对于上面的代码,a=2,因为leftMax和rightMa,两次。每次都是求mid,L…R分为两部分,所以b=2
四 哈希表
对于哈希表
HashMap, HashSet,认为增删改查操作,都为O(1)
treeMap, treeSet(里面的值按序排序),认为增删改查,都为logN
Note
java基础类型与String按值传递, String传递的是空间地址的值。
除了基础类型之外的类型是按引用传递。对于Integer,当值范围为-127到127时,按值传递。