一、链表
非连续非顺序的存储结构,元素的逻辑顺序是靠元素的指针指向次序。每个节点由data部分和链部分next,next指向下一个节点,如果要增加或删除结点,只需要改变指针方向即可。
1、删除链表的倒数第N个节点,要求一次遍历完成。(快慢指针)
思想:设置快慢指针,快指针先走N步,此时快慢指针一起走,当快指针走到链表尾节点时,慢指针的下个节点就是要删除的倒数第N个节点,因此改变慢指针的next指针指向起next的next节点,就相当于删除了倒数第N个节点。
public class Node {
int data;
Node next;
public Node(int data) {
this.data = data;
}
public void setData(int data) {
this.data = data;
}
public int getData() {
return data;
}
public void setNode(Node next) {
this.next = next;
}
public Node getNode() {
return next;
}
}
public Node removeNthFromEnd(Node head , int n ) {
Node first = head;
int length = 0;
if (head == null){
return null;
}
while (first != null) {
length++;
first = first.next;
}
if (n < 0 || n > length){
return head;
}
Node fast = head;
Node slow = head;
while (n-- != 0 && n < length) {
fast = fast.next;
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return head;
}
2、实现两个有序的链表合并为一个有序链表
思想:新建一个链表,当两个链表不为空时,循环比较两个链表的头节点,将值小的添加到新的链表中,注意插入到新链表的节点为新链表的尾节点。如果一个链表为空,那么将另一个链表接到新链表的末尾。 非递归
public Node mergeTwoLink(Node l1 , Node l2) {
Node l3 = new Node(0);
Node temp = l3;
while (l1 != null && l2 != null) {
if (l1.data < l2.data) {
temp.next = l1;
temp = temp.next;
l1 = l1.next;
}else {
temp.next = l2;
temp = temp.next;
l2 = l2.next;
}
}
if (l1 == null) {
temp.next = l2;
}
if (l2 == null) {
temp.next = l1;
}
return l3.next;
}
递归写法
public Node diguiMerge(Node l1 , Node l2) {
if (l1==null || l2==null){
return l1!=null ? l1 : l2;
}
if (l1.data < l2.data ){
l1.next = diguiMerge(l1.next,l2);
}else {
l2.next = diguiMerge(l1,l2.next);
}
return l1.data < l2.data ? l1:l2;
}
3、实现单链表反转
递归思想:从尾节点开始反转各个节点的指针域指向。
public Node getReverse(Node head) {
if (head == null || head.next == null) {
return head;
}
Node next = head.next;
Node new_node = getReverse(next);
next.next = head;
head.next = null;
return new_node;
}
非递归思想:准备两个节点,一个指向当前节点的上一个节点,一个指向当前节点的下一个节点,改变当前节点的next指向上一个节点,然后移动三个指针往后移动一步,依次改变当前节点的指向为上一个节点,直到当前节点为空为止。
public Node reverseLinkNode(Node head) {
Node pre = null;
Node next = null;
while (head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
4、判断链表中是否有环
思想:设定快慢指针,快指针比慢指针始终多走两步,即快指针走两步,慢指针走一步。如果有环,快指针的速度是慢指针的两倍,即当快慢指针相遇时证明链表中有环。
public boolean haveLoop(Node head) {
Node fast = head;
Node slow = head;
if (fast == null || slow == null ){
return false;
}
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
return true;
}
}
return false;
}
5、给出入环口节点
思想:从表头设立个指针,从相遇点设立个指针,两个指针同时移动,肯定能在入环口相遇。 算术推算过程: 相遇时slow共移动了s步,则fast移动了2s步
a:表头到入环扣的距离
x:入环口到移动b步到达相遇点
r:环的长度
L:链表的长度
其中L=a+r 快慢指针相遇时,fast比slow多走了n个圈,即nr,因此: s=a+x 2s=s+nr => s=nr => nr=a+x => a+x=(n-1)r+r => a+x=(n-1)r+L-a => a=(n-1)r+L-a-x 因此表头到入环口的距离等于相遇点到入环口的距离。所以从表头设立一个指针,从相遇点设立一个指针,两个指针同时移动,当两个指针相遇时,相遇点即为入环口。
public Node getLoopNode(Node head) {
Node fast = head;
Node slow = head;
if (fast == null || slow == null ){
return null;
}
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
while (fast == slow) {
fast = head;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
return null;
}
环的长度思想:相遇点记长度为1,快慢指针同时移动,每次移动一个长度加一,当再次相遇时即走完了整个环。
public int getListLength(Node head) {
Node fast = head;
Node slow = head;
int len = 0;
while (fast != null || fast.next != null){
fast = fast.next.next;
slow = slow.next;
while ( fast == slow) {
len = 1;
while (fast != slow) {
len++;
fast = fast.next.next;
slow = slow.next;
}
return len;
}
}
return len;
}
6、双向链表
LRU:最新最久未使用,常用于页面置换算法,是为虚拟页式存储管理服务的。
计算机的缓存容量有限,如果缓存满了就要删除一些内容,给新内容腾位置。但问题是,删除哪些内容呢?我们肯定希望删掉哪些没什么用的缓存,而把有用的数据继续留在缓存里,方便之后继续使用。LRU算法的提出是基于这样一个事实:在前面几条指令中使用频繁的页面很可能在后面的指令中被频繁使用,反过来说,已经很久没有使用的页面很可能在未来较长一段时间内不会被用到。
LRU缓存可使用一个HashMap和双向链表实现。HashMap,使得get的时间是O(1);双向链表使节点添加/删除操作O(1)。get(key)——如果键存在于缓存中,则获得键值(总是正数),否则返回-1。 set(key, value)——如果键不存在,则设置或插入值。当缓存达到其容量时,应在插入新项之前使最近最少使用的项无效。
package com.usergrowth.testCase.DoubleLink;
import java.util.HashMap;
/**
* @author tiantian.ytt
* @date 2021-10-13
*/
public class LRUCache {
int capacity;
HashMap<Integer, Node> map = new HashMap<Integer, Node>();
Node head=null;
Node end=null;
public LRUCache(int capacity) {
this.capacity = capacity;
}
/**
* 访问过的,将其在列表中删除移动到头节点
* @param key
* @return
*/
public int get(int key) {
if(map.containsKey(key)){
Node n = map.get(key);
remove(n);
setHead(n);
return n.value;
}
return -1;
}
/**
* 移除结点
* @param n
*/
public void remove(Node n){
if(n.pre!=null){
n.pre.next = n.next;
}else{
head = n.next;
}
if(n.next!=null){
n.next.pre = n.pre;
}else{
end = n.pre;
}
}
/**
* 头插法
* @param n
*/
public void setHead(Node n){
n.next = head;
n.pre = null;
if(head!=null)
head.pre = n;
head = n;
if(end ==null)
end = head;
}
/**
* 如果键不存在,则设置或插入值,若存在,则更换该结点的值,将其从队尾删除移动到队首
* 当缓存达到其容量时,将map中第一个数字删除,将队尾元素删除,将其插入到队首且存入到map中
* @param key
* @param value
*/
public void set(int key, int value) {
if(map.containsKey(key)){
Node old = map.get(key);
old.value = value;
remove(old);
setHead(old);
}else{
Node created = new Node(key, value);
if(map.size()>=capacity){
map.remove(end.key);
remove(end);
setHead(created);
}else{
setHead(created);
}
map.put(key, created);
}
}
}
二、数组
三、排序
www.cnblogs.com/morethink/p… \
1、冒泡排序
思想:从第一个元素开始,依次对相邻元素进行比较,如果第一个比第二个大就交换元素,经过一趟排序后最大的元素排在了最后。重复以上步骤,除了上趟已经沉到末尾的元素。
public static void bubbleSort(int[] a) {
for (int i=0 ; i<a.length -1 ; i++) {
for (int j=0 ; j<a.length -i -1; j++) {
if (a[j] > a[j+1]) {
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
public static void bubbleSort2(int[] a) {
for (int i = 0 ; i < a.length-1 ; i++) {
for (int j = i+1 ; j> 0 ; j--) {
if (a[j]<a[j-1]) {
int temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
}
}
}
}
平均时间复杂度O(n^2),空间复杂度O(1)
2、选择排序
思想:从第一个元素开始,将其后的元素与它进行比较找到最小的那个,与它进行交换。直到最后一个元素。
public static void selectSort(int[] a) {
for (int i=0 ; i<a.length; i++) {
int min = i;
for (int j = i+1 ; j < a.length; j++) {
if (a[j]<a[min]) {
min = j;
}
}
if (min != i) {
int temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
}
平均时间复杂度O(n^2),空间复杂度O(1)
3、直接插入排序
思想:从第二个元素开始,将其作为基准元素,将其与前边的元素依次进行比较,如果小于前边元素,将前边元素往后移动,直到找到小于或等于该基准元素的位置,将基准元素插入到该位置后边。
public static void insertSort(int[] a) {
for (int i = 1; i < a.length; i++) {
int key = a[i];
int j;
for (j = i; j > 0 && key < a[j - 1]; j--) {
a[j] = a[j - 1];
}
a[j] = key;
}
}
平均时间复杂度O(n^2),空间复杂度O(1)
4、快速排序
思想:从数组中取出一个数作为基准,通过一趟排序比它小的数据都排在左侧,比它大的数据都排在右侧。再对左侧和右侧数据进行同样的排序过程,直到区间只有一个数。 排序过程:设定两个左右指针,左指针指向最左侧,右指针指向最右侧。比较右指针的值比基准值大的话,向左移动,直到比基准值小停止移动,此时比较左指针和基准值,左指针的值比基准小的话,向右移动,直到比基准值大停止移动,交换两个指针的值。重复该过程直到i>=j,让指针i指向的值与基准值进行交换。
public static void quikSort(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int key = arr[left];
int i = left;
int j = right;
while (i < j) {
while (arr[j] >= key && i<j) {
j--;
}
while (arr[i] <= key && i<j) {
i++;
}
if (i<j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
arr[left] = arr[i];
arr[i] = key;
quikSort(arr,left,i-1);
quikSort(arr,i+1,right);
}
平均时间负责度O(nlog2n),空间负责度O(1)
5、归并排序
6、希尔排序
7、堆排序
8、基数排序
四、堆栈
五、动态规划
六、树
定义
n个节点的有限集,n=0时称为空树。在任意一棵非空树中:
- 有且仅有一个特定的称为根(Root)的结点;
- 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、......、Tn,其中每一个集合本身又是一棵树,并且称为根的子树。
结点的度
结点所有子树的个数称为该结点的度,如图A的度为3,B的度为2
树的度
树中所有结点的度的最大值称为树的度,如图树的度为3
节点的层次
节点的层次是从根开始定义的,根为第一层,根的孩子为第二层
树的深度
树中节点的最大层次称为树的深度或高度
二叉树
定义
二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。
满二叉树
在一棵二叉树中,如果所有分支节点都存在左子树和右子树,并且所有的叶子都在同一层上,这样的二叉树称为满二叉树
完全二叉树
对一棵具有n个节点的二叉树按层序进行编号,如果编号为i的节点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
二叉树的存储
顺序存储:顺序存储充分利用满二叉树的特性,即每层的节点数分别为1、2、4、8。。。2^(i-1),一个深度为i的二叉树最多只能包含-1个节点,因此只要定义一个长度为-1的数组即可存储这颗二叉树。对于普通的不是满二叉树的,那些空出来的节点对应的数组元素留空即可,因此顺序存储会造成一定的空间浪费
二叉链表存储:二叉链表存储的思想是让每个节点都记住它的左、右两个子节点,为每个节点增加left、right两个指针,分别引用该节点的左、右两个子节点。每个节点大致有如下定义:
class Node{
T data;
Node left;
Node right;
}
二叉树的遍历
前序遍历
先访问根结点,再访问左子结点,最后访问右子结点,即根左右 递归实现
public static void preOrder(TreeNode tree) {
if (tree == null) {
return;
}
System.out.print(tree.data + "\t");
preOrder(tree.left);
preOrder(tree.right);
}
非递归实现
/**
* 非递归先序遍历:
* 1、先入栈根结点,将根结点出栈,输出根结点的值,再入栈右结点、左结点
* 2、出栈左结点,输出其值,再入栈该左结点的右结点、左结点,直到遍历完该左结点所在子树
* 3、出栈右结点,输出其值,再入栈该右结点的右结点、左结点,直到遍历完该右结点的所在子树
* @param root
*/
public static void preOrderNo(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
if (root != null ){
stack.push(root);
}
while (! stack.isEmpty()) {
TreeNode node = stack.pop();
System.out.print(node.data + "\t");
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
}
中序遍历
先访问左子结点,再访问根结点,最后访问右子结点,即左根右 递归实现
public static void inOrder(TreeNode tree) {
if (tree == null) {
return ;
}
inOrder(tree.left);
System.out.print(tree.data + "\t");
inOrder(tree.right);
}
非递归实现
/**
* 非递归中序遍历
* 1、从根结点出发一路向左,入栈所有的结点
* 2、出栈一个结点并输出其值,查询该结点是否有右结点,
* 如果有则从右结点一路向左入栈右结点所在子树的所有左结点,
* 若无则出栈下一个结点,输出该结点值,重复2步骤
* 3、直到结点为null其栈为空
* @param root
*/
public static void inOrderNo(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
while (root != null || !stack.empty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
if (!stack.empty()) {
TreeNode node = stack.pop();
System.out.print(node.data + "\t");
root = node.right;
}
}
}
后序遍历
先访问左结点,再访问右结点,最后访问根结点,即左右根 递归实现
public static void proOrder(TreeNode tree) {
if (tree == null) {
return ;
}
proOrder(tree.left);
proOrder(tree.right);
System.out.print(tree.data + "\t");
}
非递归实现
/**
* 非递归后续遍历
* 1、借助两个栈,先将根结点如栈1,然后将根结点出栈1入栈2,再将根结点左结点、右结点入栈1
* 2、出栈1右结点,将右结点入栈2,再入栈该结点的左结点、右结点到栈1,直到遍历完右子树的所有节点
* 3、出栈1左结点,将左结点入栈2,再入栈该结点的左结点、右结点到栈1,直到遍历完左子树的所有结点
* 4、直到栈1为空
* 5、循环出栈2结点,并输出其值,直到栈2为空
* @param root
*/
public static void proOrderNo(TreeNode root) {
Stack<TreeNode> s1 = new Stack<>();
Stack<TreeNode> s2 = new Stack<>();
s1.push(root);
while (!s1.empty()) {
TreeNode node = s1.pop();
s2.push(node);
if (node.left != null) {
s1.push(node.left);
}
if (node.right != null) {
s1.push(node.right);
}
}
while (!s2.empty()) {
System.out.print(s2.pop().data+"\t");
}
}
层序遍历(广度优先遍历)
先访问第一层结点,再访问第二次结点,一直访问到最后一层。同一层结点的访问按照从左到右的顺序。 递归实现
public static void layerOrder(TreeNode tree) {
Queue<TreeNode> queue = new ArrayDeque<>();
if (tree != null) {
queue.offer(tree);
}
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
System.out.print(node.data + "\t");
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
七、查找
1、二分查找
思想:针对有序序列,将表中间值与要查找的元素进行比较,如果等于要查找的元素,则查找成功,否则将表分成左右两个字表,小于要查找的元素时,从右表查,大于要查找的元素的话从左表查,重复上述过程,直到子表不存在即(右指针大于左指针) 非递归
public static int getNthNum(int[] arr , int key) {
int low = 0;
int hight = arr.length-1;
int middle = 0;
while (low <= hight) {
middle = (low + hight) /2;
if (arr[middle] < arr[key]) {
low = middle+1;
}else if (arr[middle] > arr[key]) {
hight = middle-1;
}else {
return middle;
}
}
return -1;
}
递归实现
public static int getNthNumByRecur(int[] arr, int low, int hight , int key) {
if (low > hight) {
return -1;
}
int mid = (low + hight) / 2;
if (arr[mid] == arr[key]) {
return mid;
}else if (arr[mid] < arr[key]) {
return getNthNumByRecur(arr,mid+1 , hight,key);
}else {
return getNthNumByRecur(arr, low, mid-1 , key);
}
}
八、递归
汉诺塔问题:三个塔座A,B,C,刚开始所有盘子按照大的在下小的在上叠在A上,要求将A座上的盘子都移动到C座上,每次只能移动一个盘子且每个盘子都不能放在比自己小的盘子上
思想:解决n块盘子从A放在C上,只需要将n-1个盘子放在B上,第n个盘子移动到C上,最后再将n-1个盘子从B放到C上。问题转为如何将n-1个盘子从A到B上,使用同样的方法:将n-2个盘子从A放到C,再将第n-1盘子放到B上,最后将n-1个盘子放到B上。依次递归进行,剩最后一个盘子。
public static void hanTower(int n, String from, String inner, String to) {
if (n == 1) {
System.out.println("将" + n + "号盘子从" + from + "移动到" + to);
} else {
hanTower(n - 1, from, to, inner);
System.out.println("将" + n + "号盘子从" + from + "移动到" + to);
hanTower(n - 1, inner, from, to);
}
}
九、字符串
十、哈希
十一、图
十二、复杂度
十三、编程题
1、 给你一个整数n. 从 1 到 n 按照下面的规则打印每个数:
如果这个数被3整除,打印fizz,
如果这个数被5整除,打印buzz,
如果这个数能同时被3和5整除,打印fizz buzz。
样例: n = 15 ,
返回一个字符串数组: [ "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", "fizz buzz" ]
public static List<String> getPriStr(int n) {
List<String> str = new ArrayList<>();
int j;
for (int i=0; i<n ; i++) {
j = i+1;
if (j % 3 == 0 && j%5 != 0) {
str.add("fizz");
}else if (j%5 == 0 && j%3 != 0) {
str.add("buzz");
}else if (j%3 ==0 && j%5 == 0) {
str.add("fizz buzz");
}else {
str.add(String.valueOf(j));
}
}
return str;
}
System.out.println("str:" + JSON.parseArray(JSON.toJSONString(getPriStr(n))).toString());
2、 实现两个 字符串小数的加法
样例: Input: "123.7899" "2321.233" Output: "2445.0229"
public static BigDecimal getAddStr(String str1 ,String str2) {
double a= 123.7899d;
double b = 2321.2330d;
double c = a+b;
System.out.println("a1+b1:"+ c);
BigDecimal a1 = new BigDecimal(str1);
BigDecimal b1 = new BigDecimal(str2);
BigDecimal c1 = a1.add(b1);
return c1;
}
为什么两个小数相加会得到小数点后很多位,因为java里float或者double类型的变量相加都有这种情况,是二进制的问题。
double、long、float、int数据类型
int: 在JAVA中是固定的32字节,表示的范围为-2^32 ~ 2^32 即:10位 -2147483648 ~ 214748364
long: 在JAVA中是固定的64字节,表示的范围为-2^64 -- 2^64 即:19位 -9223372036854775808 ~ 9223372036854775807
float: 在JAVA中所占位不固定,表示的范围为:2^-149 ~ 2^128-1 即:38位,1.4E-45 ~ 3.4028235E38 简单数学中的精确运算,精度为8位有效数字
float f1=1.32344435f;
输出为:
f1:1.3234444
double: 在java所在占位不固定,表示2^1074 ~ 2^1024-1 即:308位:4.9E-324 至 1.7976931348623157E308 比float位数多10倍,主要计算复杂运算和天文运算。精度是17位有效数字。
double f1=1.323444354444444456;
输出为:
f1:1.3234443544444445
可以使用Float.MAX_VALUE、FLOAT.MIN_VALUE来计算最大值最小值。
4、int 类型数 反转
样例: Input: 123 Output: 321
public static long getReverse(int x) {
long res = 0;
while (x != 0) {
int p = x % 10;
x = x / 10;
res = res * 10 + p;
}
System.out.println("---res---" + res);
if ( res > Integer.MAX_VALUE || res < Integer.MIN_VALUE) {
return 0;
}
return res;
}
5、字符串转换为整型
要求如果字面意思是一个整型数,则正常转换,否则返回失败或抛出异常.
考察点: a. 整数的正负问题。
b. 整数的大小问题,可能会溢出,怎么判断?如果溢出了,怎么处理?
c. 字符串左侧是一串0开始,怎么处理?
public static int getIntByStr(String a) {
int foo;
try {
foo = Integer.parseInt(a);
}catch (NumberFormatException e){
foo = 0;
}
return foo;
}
6、编程实现O(n)时间复杂度内找到一组数据的第K大元素
思想:从数组中取出一个数作为基准,通过一趟排序比它小的数据都排在左侧,比它大的数据都排在右侧。再对左侧和右侧数据进行同样的排序过程,直到区间只有一个数。 排序过程:设定两个左右指针,左指针指向最左侧,右指针指向最右侧。比较右指针的值比基准值大的话,向左移动,直到比基准值小停止移动,此时比较左指针和基准值,左指针的值比基准小的话,向右移动,直到比基准值大停止移动,交换两个指针的值。重复该过程直到i>=j,让指针i指向的值与基准值进行交换。
public static void quikSort(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int key = arr[left];
int i = left;
int j = right;
while (i < j) {
while (arr[j] >= key && i<j) {
j--;
}
while (arr[i] <= key && i<j) {
i++;
}
if (i<j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
arr[left] = arr[i];
arr[i] = key;
quikSort(arr,left,i-1);
quikSort(arr,i+1,right);
}
public static int getNthNum(int[] arr, int n) {
return arr[n-1];
}
7、编程实现一组数据集合的全排列
8、编程实现求阶乘n!
public static long getN(int n) {
long sum = 1L;
while (n > 0) {
sum = sum * n;
n--;
}
return sum;
}
9、求斐波那契数列\
递归
public static long getfibo(int n) {
if (n == 0 || n==1) {
return n;
}else {
return getfibo(n-2) + getfibo(n-1);
}
}
非递归
public static long getfibo(int n) {
long[] arr = new long[n+1];
arr[0] = 0;
arr[1] = 1;
for (int i = 2 ; i <= n; i++) {
arr[i] = arr[i-1] + arr[i-2];
}
return arr[n];
}