算法的稳定性是当两个相同的值出现的时候。在排序过后,是否还能保持原来的相对位置。
选择排序
每次找出最小的值放到起始位置
int[] arr = {5, 3, 6, 8, 1, 7, 9, 4, 2};
int temp = arr[0];
for (int i = 0; i < arr.length-1; i++) {
int minPos = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minPos]) {
minPos = j;
}
}
swap(arr,i,minPos);
System.out.println("第" + i + "次:");
printArr(arr);
}
}
static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
static void printArr(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
算法的时间复杂度通常只需要查看内部最耗时间的语句
冒泡排序
int[] arr = {5, 3, 6, 8, 1, 7, 9, 4, 2};
sort(arr);
static void sort(int[] arr) {
// 外层循环控制次数
for (int i = arr.length - 1; i > 0; i--) {
for (int j = 0; j < i; j++) {
// 内层循环进行比较
if (arr[j] > arr[j + 1]) swap(arr, j, j + 1);
}
}
}
插入排序
每次将后面的值和前面的每一个数据比较进行交换。
static void sort(int[] arr) {
方法一
for (int i = 1; i < arr.length; i++) {
// 外层循环控制第i张数字和前面所有的数字进行比较
for (int j = i; j > 0; j--) {
if (arr[j - 1] > arr[j]) swap(arr, j - 1, j);
}
}
方法二,使用temp变量
for (int i = 1; i < arr.length; i++) {
int temp = arr[i];
int j;
for (j = i; j > 0 && temp < arr[j - 1]; j--) {
arr[j] = arr[j - 1];
}
arr[j] = temp;
}
}
希尔排序
跨步长进行插入排序,但是最后一次排序步长一定是1。
static void sort(int[] arr) {
for (int gap = arr.length/2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
// 外层循环控制次数
for (int j = i; j > gap - 1; j -= gap) {
// 内层循环控制步长,每次从当前位置,向前比较
if (arr[j] < arr[j - gap]) swap(arr, j, j - gap);
}
}
}
}
归并算法
一个数组中,前半部分元素和后半部分元素相对有序,设置三个指针,i位于起始位置,j位于2的位置,k位于新数组的位置。然后i和j位置进行比较,小的放到新数组中。同时对应的下标累加。
int[] arr = {1, 4, 7, 8, 3, 6, 9};
sort(arr);
}
static void sort(int[] arr) {
merge(arr);
}
static void merge(int[] arr) {
// mid是分界线,将一个部分有序数组分开
int mid = arr.length / 2;
int i = 0;
int j = mid + 1; //初始指向3
int k = 0;
int[] temp = new int[arr.length];
while (i <= mid && j < arr.length) {
if (arr[i] <= arr[j]) {
// <=是保持排序后数据的稳定性
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j < arr.length) {
temp[k++] = arr[j++];
}
printArr(temp);
}
对merge函数进行细化操作
static void merge(int[] arr, int leftPtr, int rightPtr, int rightBound) {
// 左指针起始位置,右指针起始位置,边界值(区间数据排序)
// mid是分界线,将一个部分有序数组分开
int mid = rightPtr - 1;
int i = leftPtr;
int j = rightPtr; //初始指向3
int k = 0;
int[] temp = new int[rightBound - leftPtr];
while (i <= mid && j < rightBound) {
if (arr[i] <= arr[j]) {
// <=是保持排序后数据的稳定性
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j < rightBound) {
temp[k++] = arr[j++];
}
printArr(temp);
}
这样子该函数可以测试中间区域的数据
public static void main(String[] args) {
// int[] arr = {9, 6, 11, 3, 5, 12, 8, 7, 10, 15, 14, 4, 1, 13, 2};
int[] arr = {1, 4, 7, 8, 3, 6, 9};
sort(arr, 0, 7);
printArr(arr);
}
static void sort(int[] arr, int left, int right) {
// 递归调用自身排序
if (left == right) return;
int mid = left + (right - left) / 2; //等价于(left+right)/2
// 左边排序
sort(arr, left, mid);
// 右边排序
sort(arr, mid + 1, right);
merge(arr, left, mid + 1, right);
}
static void merge(int[] arr, int leftPtr, int rightPtr, int rightBound) {
// 左指针起始位置,右指针起始位置,边界值(区间数据排序)
// mid是分界线,将一个部分有序数组分开
int mid = rightPtr - 1;
int i = leftPtr;
int j = rightPtr; //初始指向3
int k = 0;
int[] temp = new int[rightBound - leftPtr];
while (i <= mid && j < rightBound) {
if (arr[i] <= arr[j]) {
// <=是保持排序后数据的稳定性
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j < rightBound) {
temp[k++] = arr[j++];
}
// printArr(temp);
// 需要使用leftPtr作为边界值相加来修改数组中的值
for (int m = 0; m < temp.length; m++) arr[leftPtr + m] = temp[m];
}
static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
static void printArr(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
快速排序
在数据中取一个基准,然后将小于该基准的移动到左边,大的数据移动到右边。然后重复执行上面的操作,再次在左右数据中寻找基准。知道被分为一个数据的时候排序完成。
这里实现的快排思路如下,找一个基准,然后同时从左右两边开始往中间查找,当左边找到大于基准的数据,和右边找到小于基准的数据,就将这两个数据交换。
如下数据进行编写的时候最终将数据移动成功。
public static void main(String[] args) {
int[] arr = {7, 3, 2, 8, 1, 9, 5, 4, 6};
sort(arr, 0, arr.length - 1);
printArr(arr);
}
public static void sort(int[] arr, int leftBound, int rightBound) {
if (leftBound >= rightBound) return; //只有一个值或越界情况不需要处理
partition(arr, leftBound, rightBound);
}
static void partition(int[] arr, int leftBound, int rightBound) {
int pivot = arr[rightBound]; //基准取最后一个数据
int left = leftBound;
int right = rightBound - 1; //取基准前面一个数据开始比较
while (left < right) {
while (arr[left] <= pivot) left++;
while (arr[right] >= pivot) right--;
if(left<right) swap(arr,left,right); //交换位置,将小值移动到基准左边,大值移动到基准右边
}
swap(arr,left,rightBound); //将基准移动
}
但是上面的代码有问题,假设基准是比所有元素大的10,或者比所有元素小的0,那么程序就会报错。
修改如下,然后实现递归调用
while (left <= right) {//如果数组只有4,6两个数据,那么不设置<的时候,left和riht都是位置0,不会进入循环,会直接进行底部交换6,4因此错误
while (left <= right && arr[left] <= pivot) left++; //<=防止错误交换 ,将小于等于的移动
while (left <= right && arr[right] > pivot) right--; //将大于的移动
if (left < right) swap(arr, left, right); //交换位置,将小值移动到基准左边,大值移动到基准右边(原地不交换)
}
将partition函数设置返回值为left的值
public static void sort(int[] arr, int leftBound, int rightBound) {
if (leftBound >= rightBound) return; //只有一个值或越界情况不需要处理
int mid = partition(arr, leftBound, rightBound); //mid返回基准的位置
sort(arr, leftBound, mid - 1);
sort(arr, mid+1, rightBound);
}
计数排序
计数排序适用于量大但是数值的范围很小的情况。比如一万个人按照年龄进行排序,而年龄的范围粗略估值为0-100之间。计数排序首先以数据的取值范围创建一个长度数组(0-100),初始值数组值均为0.然后从一万个人中依次读取数据,当读到0就将下标0的位置值累加,读到60的时候就将下标60的位置值累加。当数据遍历完成后,创建一个新的数组,将计数完成的数组中的值依次读取出来,比如0的位置3个,就创建三个为0的元素。
public static void main(String[] args) {
// 定义数组,取值范围0-9
int[] arr = {2, 4, 2, 3, 7, 1, 1, 0, 0, 5, 6, 9, 8, 5, 7, 4, 0, 9};
int[] result = sort(arr);
System.out.println(Arrays.toString(result));
}
static int[] sort(int[] arr) {
int[] result = new int[arr.length]; //结果数组长度和初始数组等长
int[] count = new int[10]; //范围0-9之间数据
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++; //对应数据进行累加,代表该数据出现的次数
}
System.out.println(Arrays.toString(count));
for (int i = 0, j = 0; i < count.length; i++) {
while (count[i]-- > 0) {
result[j++] = i;
}
}
return result;
}
基数排序
public static void main(String[] args) {
int[] arr = {421, 240, 115, 532, 305, 430, 124};
int[] result = sort(arr);
System.out.println(Arrays.toString(result));
}
static int[] sort(int[] arr) {
int[] result = new int[arr.length];
int[] count = new int[10];
for (int i = 0; i < 3; i++) {
// 当前数据都是3位
int division = (int) Math.pow(10, i);
for (int j = 0; j < arr.length; j++) {
// 依次取数据的每一位
int num = (arr[j] / division) % 10;
count[num]++;
}
// System.out.println("数位"+Arrays.toString(count));
for (int m = 1; m < count.length; m++) {
// 前面一个数量家后面一个数量等于下一个数量,第一个位数的值不需要计算,目的保持稳定性
count[m] = count[m] + count[m - 1];
}
System.out.println("数位"+Arrays.toString(count));
// [2, 1, 1, 0, 1, 2, 0, 0, 0, 0] 2+1=3,3+1=4
// [2, 3, 4, 4, 5, 7, 7, 7, 7, 7]
for (int n = arr.length - 1; n >= 0; n--) {
int num = (arr[n] / division )% 10;
result[--count[num]] = arr[n];
}
System.arraycopy(result,0,arr,0,arr.length);
Arrays.fill(count,0);
}
return result;
}
链表相交
如何判断一个链表是否为环形链表,可以将每一个节点放在hash表中,也可以使用快慢指针。如图所示,环外5个节点,环内4个节点,使用快满指针,快指针走两步,慢指针走一步,初始时候在头部,则两个指针在环内一定会在有限圈数内相遇。当相遇的时候,将快指针重新指向头部。慢指针不动。然后两个指针每次同时走一步,则再次相遇的节点,就是环形相交的节点。
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
public static void main(String[] args) {
ListNode n1 = new ListNode(1);
n1.next = new ListNode(2);
n1.next.next = new ListNode(3);
n1.next.next.next = new ListNode(4);
n1.next.next.next = n1.next;
System.out.println(detectCycle(n1).val); //2
}
static ListNode detectCycle(ListNode head) {
if(head==null || head.next ==null || head.next.next==null) return null;
ListNode n1 = head.next; // 慢指针
ListNode n2 = head.next.next; // 快指针
while(n1!=n2) {
if(n2.next==null||n2.next.next==null){
return null;
}
n1 = n1.next;
n2 = n2.next.next;
}
// 退出循环代表环内相遇
n2 = head; //快指针头部重新走
while(n1!=n2){
n1=n1.next;
n2=n2.next;
}
return n1; //退出循环,返回环形链表相交的点
}
两个无环的链表,获取第一个相交的节点,依次遍历长度和尾节点,然后先判断尾节点是否是一个内存地址,如果不是直接返回false。否则长链表先走差值步,差值步长链表长度-短链表长度。然后短链表和长链表第一个相遇的节点就是两个链表相交位置。
public static void main(String[] args) {
ListNode n1 = new ListNode(1);
n1.next = new ListNode(2);
n1.next.next = new ListNode(3);
n1.next.next.next = new ListNode(4);
ListNode n2 = new ListNode(5);
n2.next = n1.next.next;
System.out.println(noLoop(n1, n2).val);
}
static ListNode noLoop(ListNode head1, ListNode head2) {
if (head1 == null || head2 == null) return null;
ListNode cur1 = head1;
ListNode cur2 = head2;
int n = 0;
while (cur1.next != null) {
n++;
cur1 = cur1.next;
}
while (cur2.next != null) {
n--;
cur2 = cur2.next;
}
// 遍历结束后判断尾节点是否为一个引用地址
if (cur1 != cur2) return null;
cur1 = n > 0 ? head1 : head2; //将长链表保存
cur2 = cur1 == head1 ? head2 : head1;//将短链表保存
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next; //长链表移动
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
// 相交
return cur1;
}
判断两个链表环形链表相交返回第一个节点。
static ListNode bothLoop(ListNode head1, ListNode loop1, ListNode head2, ListNode loop2) {
ListNode cur1 = null;
ListNode cur2 = null;
if (loop1 == loop2) {
//图二情况
cur1 = head1;
cur2 = head2;
// 两个链表环形位置处提前相交,代表之前两个链表已相交
int n = 0;
while (cur1 != loop1) {
cur1 = cur1.next;
n++;
}
while (cur2 != loop1) {
cur2 = cur2.next;
n--;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n > 0) {
cur1 = cur1.next;
n--;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
} else {
cur1 = loop1.next;
//相交或不相交。相交的点位于环形链表环上
while (cur1 != loop1) {
// 第一个节点走如果走到自身的环形节点处,代表两个节点不相交
if (cur1 == loop2) return loop1;
cur1 = cur1.next;
}
return null;
}
}
非递归先序遍历
非递归的先序遍历,先将第一个节点入栈,然后栈不为空则取栈元素。然后执行打印元素,然后将节点的右节点先入栈,然后左节点再入栈。不断重复。
static class Node {
int val;
Node left;
Node right;
Node() {
}
Node(int val) {
this.val = val;
}
Node(int val, Node left, Node right) {
this.val = val;
this.left = left;
this.right = right;
}
}
static void PreOrderTraversal(Node root) {
System.out.println("先序遍历:");
if (root != null) {
Stack<Node> stack = new Stack<Node>();
stack.add(root);
while (!stack.isEmpty()) {
// 栈不为空,取元素
root = stack.pop();
System.out.println(root.val);
if (root.right != null) stack.push(root.right);
if (root.left != null) stack.push(root.left);
}
}
}
非递归后序
每次将第一个元素压栈。然后出栈,放到备用栈中,如何再次先左后右入栈,然后再出栈,先出右边的,放入备用栈,将该元素的左右节点入栈,依次重复。其实就是先序的根左右。然后通过入栈的时候变成左右根依次入栈。
static void posOrderTraversal(Node root) {
System.out.println("后序遍历:");
if (root != null) {
Stack<Node> stack1 = new Stack<Node>();
Stack<Node> stack2 = new Stack<Node>(); //备用栈
stack1.push(root);
while (!stack1.isEmpty()) {
root = stack1.pop();
stack2.push(root);
if (root.right != null) stack1.push(root.left);
if (root.left != null) stack1.push(root.right);
}
while(!stack2.isEmpty()){
System.out.println(stack2.pop().val);
}
}
}
非递归中序遍历
1,2,4左树先入栈,然后弹出打印,将该节点的右树入栈。
static void inOrderTraversal(Node root) {
System.out.println("中序遍历:");
Stack<Node> stack = new Stack<Node>();
while (!stack.isEmpty() || root != null) {
if (root != null) {
stack.push(root);
root = root.left; //左子树依次入栈
} else {
// 出栈
root = stack.pop();
System.out.print(root.val + " ");
root = root.right;
}
}
}
前缀树
暂时将每一个值存放到路径上,有就复用没有就创建,但是每一个节点没有信息
封装节点信息
static class TrieNode {
public int pass; //经过的数量
public int end; //以该字符结尾的数量
public TrieNode[] nexts;
public TrieNode() {
pass = 0;
end = 0;
nexts = new TrieNode[26]; // 26个字母,nexts[0] ==null代表没有走向a的路,nexts[0]!=null代表有走向a的路
}
}
当封装了每一个节点的信息,就可以查询某一个字符串是否存在于当前前缀树中,只需要判断当前字符串结束位置的字符串在节点中的end值是否不为0.除此之外,还可以判断某一个字符串前缀是否出现过,只需要判断当前查询字符串结尾字符在节点中的pass值
封装一个类实现
class Trie {
// 创建根节点
private TrieNode root;
public Trie() {
root = new TrieNode(); //初始化
}
}
insert插入方法
// 插入
public void insert(String word) {
// word插入的字符,'abc'==>'a','b','c'
if (word == null) {
return;
}
char[] chs = word.toCharArray(); //转换数组保存
int index = 0;
TrieNode node = root;
node.pass++; //初始化的时候根节点的经过数量值为1
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a'; //计算ascii码差值,只有小写字母,范围0-25
// 判断当前路径是否存在,
if (node.nexts[index] == null) {
node.nexts[index] = new TrieNode();
}
node = node.nexts[index]; //node移动到新节点的位置
node.pass++; //经过的节点累加
}
node.end++;
}
search查找方法
查询字符串abc出现的次数,必须以c结尾,不包括abcd
// 查询一个字符串出现了几次
public int search(String word) {
if (word == null) {
return 0;
}
char[] chs = word.toCharArray();
int index = 0;
TrieNode node = root;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.nexts[index] == null) {
// 不存在该路径
return 0;
}
node = node.nexts[index];
}
return node.end;
}
prefixNumber前缀字符串出现的次数
// 查询一个前缀出现的次数
public int prefixNumber(String pre) {
if (pre == null) {
return 0;
}
char[] chs = pre.toCharArray();
int index = 0;
TrieNode node = root;
for (int i = 0; i < chs.length; i++) {
index = chs[i] = 'a';
if (node.nexts[index] == null) {
// 不存在该路径
return 0;
}
node = node.nexts[index];
}
return node.pass;
}
delete删除
删除思想沿途遍历,将每个节点pass–,当遍历到最后一个位置的时候再将end–;需要考虑最后一个节点的pass值为0的情况,代表没有节点经过,需要释放。
public void delete(String word) {
if (search(word) != 0) {
// 查询字符串出现过才进行删除
char[] chs = word.toCharArray();
int index = 0;
TrieNode node = root;
// 根节点pass--
node.pass--;
for (int i = 0; i < chs.length; i++) {
index = chs[i] = 'a';
// 沿途节点pass--
if (--node.nexts[index].pass == 0) {
// 释放节点
node.nexts[index] = null;
return;
}
node = node.nexts[index];
}
node.end--;
}
}
暴力递归
把问题转化为规模缩小了的同类问题的子问题,有明确的不需要继续进行递归的条件(base case),有当得到了子问题的结果之后的决策过程,不记录每一个子问题的解
汉诺塔问题:将n个圆盘移动到另一个盘上。可以先考虑3个盘的情况,都是将最大的盘优先移动到目的柱上,那么就必须将1-i-1个盘全部移动到备用柱上,然后再移动会目的柱上。
打印一个字符串所有的子集
static void process(char[] str, int i) {
if (i == str.length) {
System.out.println(String.valueOf(str));
return;
}
process(str, i + 1);
char temp = str[i];
str[i] = 0;//不可见字符串
process(str, i + 1);
str[i] = temp;
}
static void printAllSubsquence(String str) {
char[] chs = str.toCharArray();
process(chs, 0);
}
给出一个字符串的全部排列,并且要求不重复。代码先不断递归,当递归结束后,会回溯回去继续执行for循环继续递归。
static ArrayList<String> Permutation(String str) {
ArrayList<String> res = new ArrayList<>();
if (str == null || str.length() == 0) return res;
char[] chs = str.toCharArray();
process(chs, 0, res);
return res;
}
static void process(char[] str, int i, ArrayList<String> res) {
if (i == str.length) {
res.add(String.valueOf(str));
}
boolean[] visit = new boolean[26];
for (int j = i; j < str.length; j++) {
// 外层if语句进行去重操作,边遍历边去重
if (!visit[str[j] - 'a']) {
visit[str[j] - 'a'] = true;
swap(str, i, j);
process(str, i + 1, res);
swap(str, i, j); //交换原数
}
}
}
static void swap(char[] str, int i, int j) {
char temp = str[i];
str[i] = str[j];
str[j] = temp;
}
测试abc字符串返回如下
先手函数,可以先抽左边或者右边,然后后手只能从剩余的牌中抽
后手函数
public static void main(String[] args) {
int[] arr = {1,2,100,4};
System.out.println(win(arr));
}
static int win(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
// 玩家a和玩家b初始化抽牌范围,取获胜者分数
return Math.max(f(arr, 0, arr.length - 1), s(arr, 0, arr.length - 1));
}
// 先手函数
static int f(int[] arr, int i, int j) {
if (i == j) {
return arr[i];
}
// 玩家当前的分数一定需要累加,arr[i]+s(arr, i + 1, j)玩家抽取左边牌后下轮是后手
// arr[j]+ s(arr, i, j - 1))玩家抽取右边牌后,下一轮是后手,选两次后手的最大值取最优解
return Math.max(arr[i]+s(arr, i + 1, j),arr[j]+ s(arr, i, j - 1));
}
// 后手函数
static int s(int[] arr, int i, int j) {
if (i == j) return 0;
// 对手每次先抽取最大的,那么后手抽就需要取最小
return Math.min(f(arr, i + 1, j), f(arr, i, j - 1));
}
先完成下面功能,给定一个栈,如何获取栈底元素3
// 取栈底元素
static int f(Stack<Integer> stack) {
int res = stack.pop(); //出栈顶元素
if (stack.isEmpty()) {
return res;
} else {
int last = f(stack);
stack.push(res);
return last;
}
}
Stack<Integer> stack = new Stack<>();
stack.push(3);
stack.push(2);
stack.push(1);
int res = f(stack);
System.out.println(res); //3
那么再次基础上取栈底元素后,每次递归调用下面函数即可
// 反转栈
static void reverse(Stack<Integer> stack) {
if (stack.isEmpty()) {
return;
} else {
int res = f(stack); //取栈底元素
reverse(stack);
stack.push(res); //入栈的时候相当于逆序入栈 :1 2 3 =》 3 2 1
}
}
public static int process1(int[] weights, int[] values, int i, int alreadyWeight, int bag) {
// i ....的货物自由选择,形成最大价值返回,重量不超过给定bag,之前的决定组成的重量是alreadyWeight
if (alreadyWeight > bag) {
return 0;
}
if (i == values.length) {
// 没有重量的,与之对应的最大价值为0
return 0;
}
// 第一个参数代表不需要获取i号货物的价值,因此alreadyWeight也不需要累加
// 第二个参数代表需要获取i号获取的加载,因此需要累加当前values[i]的价值并且alreadyWeight累加当前货物的重量
return Math.max(process1(weights, values, i + 1, alreadyWeight, bag),
values[i] + process1(weights, values, i + 1, alreadyWeight + weights[i], bag));
}
不断的每一行去试结果,当前行试,则将当前行和之前试过的行进行处理比较。
public static void main(String[] args) {
int res = num(8);
System.out.println(res);
}
//n皇后问题
static int num(int n) {
if (n < 1) {
System.out.println("N*N棋盘不符合要求");
return 0;
}
// N*N的棋盘大小
int[] record = new int[n]; //记录每行皇后的位置放在第几列
// 从第0行开始处理,记录的信息放在record中,不能超过n行
return haunghou(0, record, n);
}
static int haunghou(int i, int[] record, int n) {
if (i == n) return 1; //执行到递归结束条件了,只有一个符合要求
int res = 0;
for (int j = 0; j < n; j++) {
// 当前i行的皇后,放在j列上,需要i-1之前的行中的皇后位置进行比较,共行共列共斜线则不符合要求
if (isValid(record, i, j)) {
record[i] = j; //符合要求,当前行上对应的列放皇后
res += haunghou(i+1, record, n); //从下一行开始比较处理
}
}
return res;
}
static boolean isValid(int[] record, int i, int j) {
// i 是当前行需要放皇后的位置,需要比较之前所有行中的情况,j是当前摆放的列
// 只需要比较 [0,...i-1]的皇后位置,不需要比较 [i,... ]往后的位置
for (int k = 0; k < i; k++) {
// 同列的情况或者同斜线
if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {
return false;
}
}
return true;
}
例题
如图所示,1和2的情况,左树为空或者只有一种情况,因此变化的只有右树。第一个树F(n-1)是减去根节点,第二个树减F(n-2)分别减去根节点和左节点。第三个数中左树右两个节点,因此有两种固定的情况,所以是F(2)
static int test1(int n) {
if (n < 0) { //没有节点,没有情况
return 0;
}
if (n == 0) { //一种情况,空树
return 1;
}
if (n == 1) { //一个节点
return 1;
}
if (n == 2) { //两个节点的情况
return 2;
}
int res = 0;
for (int leftNum = 0; leftNum <= n - 1; leftNum++) {
int leftWays = test1(leftNum); //左树情况
int rightWays = test1(n - 1 - leftNum); // 减去根节点和左树节点
res += leftWays * rightWays;
}
return res;
}
// 动态规划
static int dtest1(int n) {
if (n < 2) return 1;
int[] dp = new int[n + 1];
dp[0] = 1; //节点为0的时候只有一种情况
for (int i = 1; i < n + 1; i++) { //节点的数量
for (int j = 0; j <= i - 1; j++) {
dp[i] += dp[j] * dp[i - j - 1]; //左树的情况*右树的情况
}
}
return dp[n];
}
使用一个变量计算count,当出现左括号的时候累加,出现右括号的时候累减,那么当count小于0的时候,代表缺少左括号,当count最后大于0,则代表缺少右括号,那么使用ans变量接收计算需要添加多少个括号,当count小于0的时候,ans就累加,然后count=0,当count最后大于0,那么ans也累加多的数量。
// 括号
static int needPraentheses(String str){
int ans = 0;// 需要添加左括号的变量统计
int count =0; //统一统计,出现左括号就累加,反之累减
for(int i =0;i<str.length();i++){
if(str.charAt(i) =='('){
count++;
}else {
// 右括号多的情况,需要补左括号
if(count==0) {
// 当执行到这步,代表ans需要补
ans++;
} else {
count --;
}
}
}
// 这里count可能为0或者必定大于0,那么会统计左括号和右括号一共补的个数
return count+ans;
}
如果两个数组的平均值一样,那么从任何一个数组中取出数组元素,都无法满足条件,假设平均值都是100,如果从a中取小于100的元素,那么a的平均值会变大,相反b的平均值会变小。如果从a中取相等的元素,那么a和b无变化,不满足条件。然后取大于100的元素,那么a的平均值会变小,b会变大不满足情况。因此不能比较两个平均值相等的情况。
如果a的平均值小于b的平均值,分别为50,100.那么从小的平均值中取数。无论如何取,100的平均值都会被影响可能会降低。
那么最后只能使用大的平均值的数组进行操作。假设a和b平均值分别为100和50.那么可以进行操作数的范围是50<?<100之间。这样子是满足条件的。但是如果想是magic操作最多,那么每次移动的元素还必须是这个范围区间中最小的值。
public static void main(String[] args) {
int[] arr1 = {10, 40, 50, 60, 70, 80};//平均值51
int[] arr2 = {2, 4, 5, 10, 20, 21};// 10
System.out.println(maxOps(arr1, arr2)); //2
}
static int maxOps(int[] arr1, int[] arr2) {
// 两个传递的数组必须有值,且不重复
double sum1 = 0; //总和
for (int i = 0; i < arr1.length; i++) {
sum1 += (double) arr1[i]; //转double使操作氛围区间更大
}
double sum2 = 0;//总和
for (int i = 0; i < arr2.length; i++) {
sum2 += (double) arr2[i];
}
// 如果平均值相等不进行处理
if (avg(sum1, arr1.length) == avg(sum2, arr2.length)) {
return 0;
}
int[] arrMore = null; //保存最大数组
int[] arrLess = null;
double sumMore = 0; //保存最大总和
double sumLess = 0;
// 处理大的平均值数组,准备移动元素
if (avg(sum1, arr1.length) > avg(sum2, arr2.length)) {
arrMore = arr1;
sumMore = sum1;
arrLess = arr2;
sumLess = sum2;
} else {
arrMore = arr2;
sumMore = sum2;
arrLess = arr1;
sumLess = sum1;
}
Arrays.sort(arrMore); //排序方便取最小数达成最大ops操作
// 创建集合,每次移动的元素插入到小平均值的数组中,元素不重复。
HashSet<Integer> setLess = new HashSet();
for (int i : arrLess) {
setLess.add(i);
}
int ops = 0;
int MoreSize = arrMore.length; //数组大小会改变
int LessSize = arrLess.length;
for (int i = 0; i < arrMore.length; i++) {
double cur = (double) arrMore[i];
// 满足条件
if (cur < avg(sumMore, MoreSize) && cur > avg(sumLess, LessSize) && !setLess.contains(cur)) {
MoreSize--;
sumMore -= cur;
LessSize++;
sumLess += cur;
setLess.add(arrMore[i]);
ops++;
}
}
return ops;
}
static double avg(double sum, int size) {
return sum / (double) size;
}
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
int []arr = {6,3,4,5,1,2};
for(int i :arr){
stack.push(i);
}
Stack<Integer> stackTemp = new Stack<>(); //临时栈
// 只给一个临时栈,让原来的栈保持降序,最大的数位于栈顶,那么临时栈中的元素需要以升序排序,最小的在栈顶
// 直到原始栈为空结束移动
while(!stack.isEmpty()){
int temp = stack.pop();
if (!stackTemp.isEmpty()&&temp >= stackTemp.peek()) {
// 出栈
while (!stackTemp.isEmpty()&&temp >= stackTemp.peek()) {
int temp2 = stackTemp.pop();
// 直接入回原来的栈
stack.push(temp2);
}
}
stackTemp.push(temp);
}
while(!stackTemp.isEmpty()){
System.out.println(stackTemp.peek());
stack.push(stackTemp.pop()); //将临时栈中排好的顺序依次入回原始栈
}
}
public static void main(String[] args) {
int num = 12258;
System.out.println(converWays(num)); //5
}
static int converWays(int num) {
if (num < 1) return 0;
return process1(String.valueOf(num).toCharArray(), 0); //从第一个字符开始规划
}
static int process1(char[] str, int index) {
if (index == str.length) return 1;
if (str[index] == '0') return 0; //以0开头的字符无法组成
// index及其后序还是数字字符,不以0开头,以1-9开头
int res = process1(str, index + 1); //决定str[index]自身为一部分
if (index == str.length - 1) {//除index之外,后序没有字符串了
// 遍历到最后一个字符,无法继续向下组成两位字符
return res;
}
// 判断第二位字符,index+1是否越界
// index+index+1组成的字符没有超过27
if (((str[index] - '0') * 10 + str[index + 1] - '0') < 27) {
res += process1(str, index + 2);
}
return res;
}
以下是动态规划的写法
static int dpways(int num) {
if (num < 1) return 0;
char[] str = String.valueOf(num).toCharArray();
int N = str.length;
int[] dp = new int[N + 1];
dp[N] = 1; //遍历到底部的情况
dp[N - 1] = str[N - 1] == '0' ? 0 : 1;
// 从后开始向前动态规划
for (int i = N - 2; i >= 0; i--) {
if (str[i] == '0') {
dp[i] = 0;
} else {
dp[i] = dp[i + 1] + (((str[i] - '0') * 10 + str[i + 1] - '0') < 27 ? dp[i + 2] : 0);
}
}
return dp[0];//动态规划结果全部保存在该数组中
}
仅用栈结构实现队列结构
static class TwoStackQueue{
private Stack<Integer> stackPush;
private Stack<Integer> stackPop;
public TwoStackQueue(){
stackPush = new Stack<Integer>();
stackPop = new Stack<Integer>();
}
public void push(int pushInt){
// 插入push栈中
stackPush.push(pushInt);
dao();
}
public int poll(){
if(stackPop.empty() && stackPush.empty()){
throw new RuntimeException("is empty");
}
dao();
return stackPop.pop();
}
public int peek(){
if(stackPop.empty() && stackPush.empty()){
throw new RuntimeException("is empty");
}
dao();
return stackPop.peek();
}
public void dao(){
if(stackPop.isEmpty()){ //必须是空栈才能入栈
while(!stackPush.isEmpty()){
stackPop.push(stackPush.pop());
}
}
}
}
仅用队列结构实现栈结构
static class TwoQueueStack {
private Queue<Integer> queue1;
private Queue<Integer> queue2;
public TwoQueueStack() {
queue1 = new LinkedList<Integer>();
queue2 = new LinkedList<Integer>();
}
public void push(int n) {
// 栈都为空的情况,入第一个栈
if(queue1.size()==0 && queue2.size()==0) {
queue1.offer(n);
}
if(queue1.size()!=0 && queue2.size()==0) {
queue1.offer(n);
}
if(queue1.size()==0 && queue2.size()!=0) {
queue2.offer(n);
}
}
public int pop() {
if (queue2.size() == 0) {
while (queue1.size() > 1) {
// 保留一个元素,其余移动到另一个队列中
queue2.offer(queue1.poll());
}
return queue1.poll();
} else {
while (queue2.size() > 1) {
// 保留一个元素,其余移动到另一个队列中
queue1.offer(queue2.poll());
}
return queue2.poll();
}
}
}
设置一个一维数组,其每一个值普遍依赖于上一行和左边的值。但是第一行和最左边的一行不依赖普遍的条件。比如h依赖c和g。但是b不依赖上一行。在这个一位数组中初始情况记录第一行的数据,然后根据普遍条件依次更新每一个一维数组的值,形成类似数组下移动的情景。然后一维数组变为fgh等。最后将最后一个一维数组的值返回即可。
如果当前值普遍依赖上一行多个值,那么一维数组更新的时候从最后一个元素开始更新
如果是如图所示的依赖情况,那么需要设置一个记录变量t,一维数组的第一行不需要处理,第二行处理的时候,一维数组从左往右处理。t记录a,然后g的位置就可以依赖出关系
假设n个数值,值的区间范围0-99.那么可以分成n+1份捅,每一个捅的区间范围固定。那么最后一定会存在一个空捅,空捅左边最相邻非空捅的最大值和右边非空捅的最小值的差值一定大于一个捅的区间,因为他们之间隔了一个空捅的区间范围。但是同一个捅内部的最大值和最小值一定小于一个捅的区间。如9和0,区间一定是在0-9之间。而这题需要的是两个相邻数的最大差值。因此每一个捅内部只需要关注最大值和最小值即可。差值在两个桶之间取出第一个桶的最大值9和第二个桶的最小值17进行比较差值,以此类推。
public static void main(String[] args) {
int[] nums = {9, 0, 17, 4, 63, 72, 65, 67, 99};
System.out.println(maxGap(nums)); // 最大差值46
}
static int maxGap(int[] nums) {
if (nums == null || nums.length < 2) {
// 为空或只有一个数值无法比较
return 0;
}
int len = nums.length;
int min = Integer.MAX_VALUE;//系统值 2147483647
int max = Integer.MIN_VALUE;//系统值 -2147483648
// 找到数组中的最大值和最小值
for (int i = 0; i < len; i++) {
min = Math.min(min, nums[i]);
max = Math.max(max, nums[i]);
}
if (min == max) return 0; //最大值和最小值相等无法比较直接退出
// 准备三维数组
boolean[] hasNum = new boolean[len + 1];//n+1个桶,判断i号捅是否进来过数字
int[] maxs = new int[len + 1]; // i号桶收集所有数字的最大值
int[] mins = new int[len + 1];// i号桶收集所有数字的最小值
int bid = 0;//默认桶号
for (int i = 0; i < len; i++) {
bid = bucket(nums[i], len, min, max); //计算桶号即对应区间
mins[bid] = hasNum[bid] ? Math.min(nums[i], mins[bid]) : nums[i];
maxs[bid] = hasNum[bid] ? Math.max(nums[i], maxs[bid]) : nums[i];
hasNum[bid] = true; //桶只要进过数就为true
}
int res = 0;
int lastMax = maxs[0]; //默认第一个的最大值
for (int i = 1; i <= len; i++) {
if (hasNum[i]) {
// 存在桶的情况下进行比较差值
res = Math.max(res, mins[i] - lastMax); //比较差值
lastMax = maxs[i]; //下一个桶的最大值
}
}
return res;
}
static int bucket(long num, long len, long min, long max) {
// 计算当前数值位于当一个桶区间内
return (int) ((num - min) * len / (max - min));
}
最大差值不一定存在空桶之间
异或和最小如下
假设最优划分下0-i位置上的情况,i一定是最后一个数,那么这最后一个数只有两种情况,异或和为0或者不为0的两种情况。
当前i位置在最优划分下异或和不是0,如上图4结尾的例子,那么当前i位置有没有都一样,取i-1位置上的值
假设当前i位置上的异或和是0,那么以i结尾计算,从k位置开始的数到i的这段范围区间异或和为0的区域。那么k一定是离i最近的数,如果k不是离i最近的,中间存在l位置,l-i位置异或和也是0,那么k-l-1位置异或和也是0。那么当前最优解会一分为二(如321123,如果作为整体则只有一个异或和为0,如果中间一分为二,则有两个异或和为0的)。因此k一定是离i最近的数
给定一个map数组进行记录,key是异或和值,value是出现的最近一次位置
当来到下标1的位置的时候,3和2的异或和是1不为0,那么当前位置的dp[i]记录为dp[i-1]位置的值,map中增加一个记录
当来到下标2的位置的时候1和1进行异或和为0, 从map中寻找异或和为0的地方其所对应的value值为-1,那么代表-1往后位置,从0开始到2结束存在最优解和如果当前dp[i]取dp[i-1]位置的最优解之间取最大值。 结束后将map中的value修改为2代表最近出现的位置,
当到下标3的位置的时候,异或和为4,map中无该值,直接添加到map中。并且当前dp[3]=dp[2]=1。当到下标4的位置的时候,异或和还是4,map中找到4出现的最近位置3,则从4开始到4结束的位置存在最优解一个,0-3位置存在最优解1一个共2个和假设当前位置异或和不是0的情况下,dp[4]=dp[3]=1之间取最大值比较。则当前dp[4]=2。以此类推
public static void main(String[] args) {
int[] arr = {3, 2, 1, 0, 1, 0, 2, 0, 3, 2, 1, 0, 4, 0};
int []arr2 = {3,2,1,4,0,1,2,3};
System.out.println(mostEOR(arr)); //7
System.out.println(mostEOR(arr2)); //3
}
static int mostEOR(int[] arr) {
int xor = 0;
// dp[i] =》arr[0-i]位置的最优划分情况下的解,异或和为0的个数是多少
int[] dp = new int[arr.length];
// key是异或和:从0开始的某个前缀异或和
// value是这个前缀异或和出现的最晚位置
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, -1); //初始情况空数组
for (int i = 0; i < arr.length; i++) {
xor ^= arr[i]; // 0-i位置进行异或和
if (map.containsKey(xor)) {
// 存在当前异或和,找到其位置value
// 使用pre记录value,pre+1~i位置最近的最优划分解,属于最后一部分划分开始的地方
// 0~pre区间的最优划分 + pre+1~i区间的最优划分即为当前i位置的最优划分数量
int pre = map.get(xor);
dp[i] = pre == -1 ? 1 : (dp[pre] + 1); //(dp[pre] + 1)前dp[pre]的最优解个数加当前最优解个数
}
if (i > 0) {
// 每次都需要进行两种情况最优解比较
dp[i] = Math.max(dp[i], dp[i - 1]);
}
map.put(xor, i);
}
return dp[arr.length - 1];
}
分别记录左上角和右下角两个位置然后每次移动的时候沿着对角线移动,当行或列错过了结束循环
public static void main(String[] args) {
int[][] arr = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};
int tR = 0;
int tC = 0; //左上角点
int dR = arr.length - 1;
int dC = arr[0].length - 1; //右下角点
while (tR <= dR && tC <= dC) {
printEdge(arr, tR++, tC++, dR--, dC--);
}
}
static void printEdge(int[][] m, int a, int b, int c, int d) {
// a,b记录左上角的行列,c,d记录右下角的行列
if (a == c) {
// 位于同一行
for (int i = b; i <= d; i++) {
System.out.println(m[a][i] + " ");
}
} else if (b == d) {
// 同列情况
for (int i = a; i <= c; i++) {
System.out.println(m[i][b] + " ");
}
} else {
// 不同边同列情况
int curC = a; //变大向下,反之向上
int curR = b; // 变大向右,反之向左
// 先向右打印,然后向下打印,然后向左打印,然后向上打印
while (curR != d) {
System.out.println(m[a][curR] + " ");
curR++;
}
while (curC != c) {
System.out.println(m[curC][d] + " ");
curC++;
}
while (curR != b) {
System.out.println(m[c][curR] + " ");
curR--;
}
while (curC != a) {
System.out.println(m[curC][b] + " ");
curC--;
}
}
}
该题目按照分层进行旋转,最外面的一层进行旋转,然后里面的数在进行旋转。然后记录初始左上角和右下角的坐标,使用四个变量记录每一组的四个值,如第一组0,3,15,12,第二组1,7,14,8。然后交换这个四个变量的位置即可。
public static void main(String[] args) {
int[][] arr = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}, {12, 13, 14, 15}};
int tR = 0;
int tC = 0; //左上角点
int dR = arr.length - 1;
int dC = arr[0].length - 1; //右下角点
while (tR < dR) {
rotateEdge(arr, tR++, tC++, dR--, dC--);
}
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[0].length; j++) {
System.out.println(arr[i][j]);
}
}
}
static void rotateEdge(int[][] m, int a, int b, int c, int d) {
int temp = 0;
for (int i = 0; i < d - b ; i++) {
temp = m[a][b + i];
m[a][b + i] = m[c - i][b];
m[c - i][b] = m[c][d - i];
m[c][d - i] = m[a + i][d];
m[a + i][d] = temp;
}
}
使用两个变量初始情况都在左上角,然后一个往右走,一个往下走,每次走一步交替打印,如都走到bf的位置,打印,然后再走到cgk的位置,交替打印kgc。如果某个点到达边界,如一个点到达e位置后,改变方向往下走,另一个点到达p位置后,改变方向向右走。
public static void main(String[] args) {
int[][] m = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};
printZigZag(m);
}
static void printZigZag(int[][] m) {
int th = 0;
int tl = 0; //第一个点的行和列
int dh = 0;
int dl = 0;//第二个点的行和列
int maxH = m.length - 1; // 最大行数
int maxL = m[0].length - 1; //最大列数
boolean flag = false; //交替打印标志
while (th != maxH + 1) {
printLevel(m, th, tl, dh, dl, flag);
// 处理第一个点的移动,先往右
th = tl == maxL ? th + 1 : th;
tl = tl == maxL ? tl : tl + 1;
// 处理第二个点的移动,先往下
dl = dh == maxH ? dl + 1 : dl;
dh = dh == maxH ? dh : dh + 1;
flag = !flag;
}
System.out.println();
}
static void printLevel(int[][] m, int tR, int tC, int dR, int dC, boolean flag) {
// 判断是否交替打印
if (flag) {
while (tR != dR + 1) {
// 从右上往左下打印
System.out.print(m[tR++][tC--] + " ");
}
} else {
// 从左下往右上打印
while (dR != tR - 1) {
System.out.print(m[dR--][dC++] + " ");
}
}
}