算法题
链表反转
将单链表的连接顺序反转过来
例:
输入:1->2->3->4->5
输出:5->4->3->2->1
使用两种方式解题
方法一:迭代
package nuc.zy.edu.day1;
/**
* @Author:皮皮
* @Date:2021/5/8 17:40
*/
//01-链表反转-迭代
public class ReverseList {
static class ListNode {
//结点的当前值
int val ;
//指向下一个结点
ListNode next ;
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
//迭代
public static ListNode iterate(ListNode head) {
//prev存放当前结点的上一个结点与下一个结点 为下个结点做铺垫
ListNode prev = null,next ;
//当前结点
ListNode curr = head ;
while (curr != null) {
next = curr.next;
curr.next = prev;
prev = curr ;
curr = next;
}
return prev ;
}
public static void main(String[] args) {
ListNode node5 = new ListNode(5,null) ;
ListNode node4 = new ListNode(4,node5) ;
ListNode node3 = new ListNode(3,node4) ;
ListNode node2 = new ListNode(2,node3) ;
ListNode node1 = new ListNode(1,node2) ;
// ListNode iterate = iterate(node1);
ListNode prev = iterate(node1);
System.out.println(prev);
}
}
方法二:递归
package nuc.zy.edu.day1;
/**
* @Author:皮皮
* @Date:2021/5/8 18:00
*/
//02-链表反转-递归
public class ReverseList2 {
static class ListNode {
//结点的当前值
int val ;
//指向下一个结点
ListNode next ;
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
//递归 从最后一个结点开始递归
public static ListNode recursion(ListNode head) {
if (head == null || head.next ==null) {
return head ;
}
ListNode new_head = recursion(head.next);
head.next.next = head ;
head.next = null ;
return new_head ;
}
public static void main(String[] args) {
ListNode node5 = new ListNode(5,null) ;
ListNode node4 = new ListNode(4,node5) ;
ListNode node3 = new ListNode(3,node4) ;
ListNode node2 = new ListNode(2,node3) ;
ListNode node1 = new ListNode(1,node2) ;
ListNode recursion = recursion(node1);
System.out.println(recursion);
}
}
素数个数统计
统计n以内的素数个数
素数:只能被1和自身整除的自然数,0、1除外
例:
输入:100
输出:25
重点考察:埃筛法
方法一:暴力算法
package nuc.zy.edu.day1;
/**
* @Author:皮皮
* @Date:2021/5/8 20:22
*/
//03-统计素数个数-暴力算法
public class Sushu {
public static void main(String[] args) {
System.out.println(bf(100));
}
//暴力算法 素数的个数
private static int bf(int n) {
int count = 0 ;
for (int i = 2; i < n; i++) {
count += isPrime(i) ? 1 : 0 ;
}
return count ;
}
//判断是否是素数
private static boolean isPrime(int x) {
for (int i = 2; i *i <= x; i++) {
if (x % i == 0) {
return false ;
}
}
return true ;
}
}
方法二:埃筛法
package nuc.zy.edu.day1;
/**
* @Author:皮皮
* @Date:2021/5/8 20:22
*/
//03-统计素数个数-暴力算法
public class Sushu {
public static void main(String[] args) {
System.out.println(eratosthenes(100));
}
//埃筛法 素数 非素数(合数) 12 = 2 * 6
public static int eratosthenes(int n) {
boolean[] isPrime = new boolean[n] ; //false代表素数
int count = 0 ;
for (int i = 2; i < n; i++) {
if (!isPrime[i]){
count++ ;
for (int j = i * i; j < n; j+=i) { //j是合数的标记位
isPrime[j] = true ;
}
}
}
return count ;
}
}
删除排序数组中的重复项
一个有序数组nums,原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。
不能使用额外的数组空间,必须在原地修改输入数组并在使用O(1)额外空间的条件下完成。
例:
输入:[0,1,2,2,3,3,4]
输出:5
重点考察:双指针算法
package nuc.zy.edu.day2;
/**
* @Author:皮皮
* @Date:2021/5/11 9:09
*/
//05-删除排序数组中的重复项
public class SortedArrayDuplicates {
public static void main(String[] args) {
System.out.println(removeDuplicates(new int[]{0,1,2,2,3,3,4}));
}
public static int removeDuplicates(int[] nums) {
//首先要保证nums不为空
if (nums.length == 0) {
return 0 ;
}
int i = 0 ; //慢指针
///快指针
for (int j = 1; j < nums.length; j++) {
if (nums[j] != nums[i]) {
i++ ;
nums[i] = nums[j] ;
}
}
return i + 1; //数组的长度
}
}
寻找数组的中心下标
给定一个整数数组nums,请编写一个能够返回数组“中心下标”的方法。
中心下标是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果数组不存在中心下标,返回-1。如果数组有多个中心下标,应该返回最靠近左边的那一个。
注意:中心下标可能出现在数组的两端。
方法一:双指针算法
package nuc.zy.edu.day2;
import java.util.Arrays;
/**
* @Author:皮皮
* @Date:2021/5/11 9:27
*/
//06-寻找数组的中心下标 双指针方法
public class ArrayCenterIndex {
public static void main(String[] args) {
System.out.println(pivotIndex(new int[]{1, 7, 3, 6, 5, 6}));
}
public static int pivotIndex(int[] nums) {
int sum = Arrays.stream(nums).sum() ;
int total = 0 ;
for (int i = 0; i < nums.length; i++) {
total += nums[i] ;
System.out.println("---"+sum);
if (total == sum) {
return i ;
}
sum -= nums[i] ;
}
return -1 ;
}
}
方法二:2sum+center==total
package nuc.zy.edu.day2;
import java.util.Arrays;
/**
* @Author:皮皮
* @Date:2021/5/11 9:51
*/
//06-寻找数组的中心下标 2sum+center==total
public class ArrayCenterIndex1 {
public static void main(String[] args) {
System.out.println(pivotIndex(new int[]{1, 7, 3, 6, 5, 6}));
}
public static int pivotIndex(int[] nums) {
int total = Arrays.stream(nums).sum() ;
int sum = 0 ;
for (int i = 0; i < nums.length; i++) {
sum += nums[i] ;
//防止数组越界
if (i + 1 !=nums.length) {
if (2 * sum + nums[i + 1] == total) {
return i + 1 ;
}
}
}
return -1 ;
}
}
x的平方根
在不使用sqrt(x)函数的情况下,得到x的平方根的整数部分
重点考察:二分法,牛顿迭代
方法一:二分法
package nuc.zy.edu.day2;
/**
* @Author:皮皮
* @Date:2021/5/11 10:21
*/
//07-x的平方根-二分查找
public class SqrtX {
public static void main(String[] args) {
System.out.println(binarySearch(24));
}
//二分查找
private static int binarySearch(int x) {
int index = -1,l = 0,r = x;
while (l <= r) {
int mid = l + (r-l)/2 ;
if (mid * mid <= x) {
index = mid ;
l = mid + 1 ;
} else {
r = mid - 1 ;
}
}
return index ;
}
}
方法二:牛顿迭代
package nuc.zy.edu.day2;
/**
* @Author:皮皮
* @Date:2021/5/11 10:21
*/
//08-x的平方根-牛顿迭代
public class SqrtX {
public static void main(String[] args) {
System.out.println(newton(25));
}
//调用牛顿迭代方法
private static int newton(int x) {
//如果是0直接返回
if (x == 0) {
return 0 ;
}
return (int)sqrt(x,x) ;
}
//牛顿迭代
private static double sqrt(double i, int x) {
double res = (x/i + i)/2 ;
if (res == i) {
return i ;
} else {
//迭代 不断寻找接近开方值
return sqrt(res,x) ;
}
}
}
三个数的最大乘积
整型数组nums,在数组中找出由三个数字组成的最大乘积,并输出这个乘积。
乘积不会越界
重点考察:线性扫描
package nuc.zy.edu.day2;
import java.util.Arrays;
/**
* @Author:皮皮
* @Date:2021/5/11 11:54
*/
//09-数组中三个数的最大乘积
public class MaxProduct {
public static void main(String[] args) {
System.out.println(sort(new int[]{1,2,3,4,5,6}));
System.out.println(getMaxMin(new int[]{1,2,3,4,5,6}));
}
//Math排序
private static int sort(int[] nums) {
//升序排序
Arrays.sort(nums);
// System.out.println(Arrays.toString(nums));
int n = nums.length ;
return Math.max(nums[0] * nums[1] * nums[n-1],nums[n-1] * nums[n-2] * nums[n-3]) ;
}
//线性扫描 不需要排序
public static int getMaxMin(int[] nums) {
int min1 = Integer.MAX_VALUE,min2 = Integer.MAX_VALUE ;
int max1 = Integer.MIN_VALUE,max2 = Integer.MIN_VALUE,max3 = Integer.MIN_VALUE ;
for (int x : nums) {
if (x < min1) {
min2 = min1 ;
min1 = x ;
} else if (x < min2) {
min2 = x ;
}
if (x > max1) {
max3 = max2 ;
max2 = max1 ;
max1 = x ;
} else if (x > max2) {
max3 = max2 ;
max2 = x ;
} else if (x > max3) {
max3 = x ;
}
}
return Math.max(min1 * min2 * max1,max1 * max2 * max3) ;
}
}
两数之和
给定一个整数数组numbers,从数组中找出两个数满足相加之和等于目标数 target 。
假设每个输入只对应唯一的答案,而且不可以重复使用相同的元素。
返回两数的下标值,以数组形式返回
package nuc.zy.edu.day2;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @Author:皮皮
* @Date:2021/5/11 16:36
*/
//10-两数之和-无序数组
public class Test {
public static void main(String[] args) {
System.out.println(Arrays.toString(solution(new int[]{1,2,3,4,5,6},10)));
System.out.println(Arrays.toString(solution1(new int[]{1,2,3,4,5,6},10)));
}
//暴力算法 O(N²)
private static int[] solution(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{i,j} ;
}
}
}
return new int[0];
}
//通过map只用循环一次 最多全部循环一次 记标记 O(N)
private static int[] solution1(int[] nums, int target) {
Map<Integer,Integer> map = new HashMap<>() ;
for (int i = 0; i < nums.length; i++) {
//containsKey 是因为key存放是数组的值
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]),i} ;
}
//key存放的是数组的值 value存放的是下标
map.put(nums[i],i) ;
}
return new int[0];
}
}
两数之和2
给定一个升序排列的整数数组numbers,从数组中找出两个数满足相加之和等于目标数 target 。
假设每个输入只对应唯一的答案,而且不可以重复使用相同的元素。
返回两数的下标值,以数组形式返回
package nuc.zy.edu.day2;
import java.lang.annotation.Target;
import java.util.Arrays;
/**
* @Author:皮皮
* @Date:2021/5/11 17:20
*/
//11-两数之和-有序数组
public class Test1 {
public static void main(String[] args) {
System.out.println(Arrays.toString(twoSearch(new int[]{1,2,3,4,5,6},10)));
System.out.println(Arrays.toString(twoPoint(new int[]{1,2,3,4,5,6},10)));
}
//二分查找
public static int[] twoSearch(int[] numbers, int target) {
for (int i = 0; i < numbers.length; i++) {
int low = i,high = numbers.length - 1 ;
while (low <= high) {
int mid = low + (high - low)/2 ;
if (numbers[mid] == target - numbers[i]) {
return new int[]{i,mid} ;
}else if(numbers[mid] > target - numbers[i]){
high = mid - 1 ;
} else {
low = mid + 1 ;
}
}
}
return new int[0] ;
}
//双指针查找
public static int[] twoPoint(int[] numbers, int target) {
int low = 0,high = numbers.length - 1 ;
while (low < high) {
int sum = numbers[low] + numbers[high] ;
if (sum == target) {
return new int[]{low,high} ;
} else if (sum < target) {
low++ ;
} else {
high-- ;
}
}
return new int[0] ;
}
}
斐波那契数列(三种解法)
求取斐波那契数列第N位的值。
斐波那契数列:每一位的值等于它前两位数字之和,前两位数字固定:0,1,1,2,3,5,8…
package nuc.zy.edu.day3;
import com.sun.org.apache.regexp.internal.RE;
import sun.print.SunMinMaxPage;
/**
* @Author:皮皮
* @Date:2021/5/22 15:35
*/
//求取斐波那契数列第N位的值。 (三种解法)
public class Fib {
public static void main(String[] args) {
System.out.println(calculate(10));
System.out.println(calculate1(10));
System.out.println(iterate(10));
// iterate(10) ;
}
//暴力递归
public static int calculate(int num) {
if (num == 0) {
return 0 ;
}
if (num == 1) {
return 1 ;
}
return calculate(num-1) + calculate(num - 2) ;
}
//去重递归
public static int calculate1(int num) {
int[] arr = new int[num + 1] ;
if (num == 0) {
return 0 ;
}
if (num == 1) {
return 1 ;
}
return recurse(arr,num) ;
}
private static int recurse(int[] arr,int num) {
if (num == 0) {
return 0 ;
}
if (num == 1) {
return 1 ;
}
if (arr[num] != 0) {
return arr[num] ;
}
arr[num] = recurse(arr,num-1) + recurse(arr,num - 2) ;
return arr[num] ;
}
//双指针迭代
private static int iterate(int num) {
if (num == 0) {
return 0;
}
if (num == 1) {
return 1;
}
int low = 0,high = 1,sum = 0;
for (int i = 2; i <= num ; i++) {
sum = low + high ;
low = high ;
high = sum ;
}
return high;
}
}
排列硬币(三种解法)
总共有n枚硬币,将它们摆成一个阶梯形状,第k行就必须正好有k枚硬币。
给定一个数字n,找出可形成完整阶梯行的总行数。n是一个非负整数,并且在32位有符号整型的范围内。
package nuc.zy.edu.day3;
/**
* @Author:皮皮
* @Date:2021/5/22 16:37
*/
//排列硬币(三种解法)总共有n枚硬币,将它们摆成一个阶梯形状,第k行就必须正好有k枚硬币。
public class ArrangeCoin {
public static void main(String[] args) {
System.out.println(arrangeCoins(14));
System.out.println(arrangeCoins1(14));
System.out.println(arrangeCoins2(14));
}
//迭代
public static int arrangeCoins(int n) {
for (int i = 1; i <= n; i++) {
n -= i ;
if (n <= i) {
return i ;
}
}
return 0;
}
//二分查找 假设有n行
public static int arrangeCoins1(int n) {
int low = 0,high = n ;
while (low <= high){
int mid = (high - low)/2 + low ;
int cost = ((mid + 1) * mid) /2 ;
if (cost == n) {
return mid ;
}else if (cost < n) {
if (n - cost < mid + 1) {
return mid ;
} else {
low = mid + 1 ;
}
}else{
high = mid - 1 ;
}
}
return 0;
}
//牛顿迭代
//二分查找 假设有n行
public static int arrangeCoins2(int n) {
if(n == 0){
return 0 ;
}
return (int)sqrt(n,n);
}
//开方求x
private static double sqrt(double x, int n) {
double res = ((x + ((2 * n) - x) / x))/2 ;
if (res == x) {
return x ;
}else {
return sqrt(res,n) ;
}
}
}
环形链表(两种解法)
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪next指针再次到达该节点,则链表中存在环
如果链表中存在环,则返回true。否则,返回false 。
package nuc.zy.edu.day3;
import java.util.HashSet;
import java.util.Set;
/**
* @Author:皮皮
* @Date:2021/5/22 17:18
*/
//环形链表 给定一个链表,判断链表中是否有环。
public class LinkCycle {
static class ListNode{
int val ;
ListNode next ;
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public static void main(String[] args) {
ListNode node5 = new ListNode(5,null) ;
ListNode node4 = new ListNode(4,node5) ;
ListNode node3 = new ListNode(3,node4) ;
ListNode node2 = new ListNode(2,node3) ;
ListNode node1 = new ListNode(1,node2) ;
// node5.next = node3 ;
System.out.println(hasCycle(node1));
System.out.println(hasCycle1(node1));
}
//利用set集合判断是否存在重复元素 时间空间复杂度O(n)
private static boolean hasCycle(ListNode head) {
Set<ListNode> set = new HashSet<ListNode>() ;
while (head != null) {
if (!set.add(head)) {
return true ;
}
head = head.next ;
}
return false ;
}
//指针算法 一块一慢 如果有环在环中终会相遇 如果没有快指针就会为null
private static boolean hasCycle1(ListNode head) {
if (head == null || head.next == null) {
return false ;
}
ListNode slow = head ;
ListNode quick = head.next ;
while (slow != quick) {
if (quick == null || quick.next == null) {
return false ;
}
slow = slow.next ;
quick = quick.next.next ;
}
return true ;
}
}
合并两个有序数组
两个有序整数数组nums1和nums2,将nums2合并到nums1中,使nums1成为一个有序数组。
初始化nums1和nums2的元素数量分别为m和n。假设nums1的空间大小等于m +n,这样它就有足够的空间保存来自nums2的元素。
package nuc.zy.edu.day4;
import java.util.Arrays;
/**
* @Author:皮皮
* @Date:2021/5/24 10:01
*/
//合并两个有序数组 两个有序整数数组nums1和nums2,将nums2合并到nums1中,使nums1成为一个有序数组。
public class MergeSortArray {
public static void main(String[] args) {
int[] nums1 = new int[]{1, 3, 5, 7, 9, 0, 0, 0, 0};
int[] nums2 = new int[]{2, 4, 6, 8};
// System.out.println(Arrays.toString(merge(nums1,5,nums2,4)));
System.out.println(Arrays.toString(merge1(nums1, 5, nums2, 4)));
}
//jdk自带的排序
private static int[] merge(int[] nums1, int m, int[] nums2, int n) {
//拷贝nums2数组 从0开始拷贝 拷贝到nums1中 从m开始拷贝n个数
System.arraycopy(nums2, 0, nums1, m, n);
Arrays.sort(nums1); //O(nlog(n)) n->m+n
return nums1;
}
//双指针O(m+n) 使用多余空间
private static int[] merge1(int[] nums1, int m, int[] nums2, int n) {
int[] nums1_copy = new int[m];
System.arraycopy(nums1, 0, nums1_copy, 0, m);
int p1 = 0; //指向nums1_copy
int p2 = 0; //指向nums2
int p = 0; //指向nums1
while (p1 < m && p2 < n) {
nums1[p++] = nums1_copy[p1] < nums2[p2] ? nums1_copy[p1++] : nums2[p2++];
}
if (p1 < m) {
System.arraycopy(nums1_copy, p1, nums1, p1 + p2, m + n - p1 - p2);
// System.arraycopy(nums1_copy,p1,nums1,p1 + n,m - p1);
}
if (p2 < n) {
System.arraycopy(nums2, p2, nums1, p1 + p2, m + n - p1 - p2);
}
return nums1;
}
//双指针 未使用多余空间
private static int[] merge2(int[] nums1, int m, int[] nums2, int n) {
int p1 = m - 1;
int p2 = n - 1;
int p = m + n - 1; //nums1的最后一个下标
while (p1 >= 0 && p2 >= 0) {
nums1[p--] = nums1[p1] > nums2[p2] ? nums1[p1--] : nums2[p2--];
}
//只可能是p2未排完需要拷贝 p1未排完也不需要拷贝 本身已经是有序的
System.arraycopy(nums2,0,nums1,0,p2 + 1); //p2本身就是下标 下标+1就是长度
return nums1 ;
}
}
子数组最大平均数
给一个整数数组,找出平均数最大且长度为k的下标连续的子数组,并输出该最大平均数。
例:
输入:[1,12,-5,-6,50,3],k= 4输出:12.75
最大平均数(12-5-6+50)/4 = 51/4 = 12.75
package nuc.zy.edu.day4;
/**
* @Author:皮皮
* @Date:2021/5/24 10:58
*/
//子数组最大平均数 给一个整数数组,找出平均数最大且长度为k的下标连续的子数组,并输出该最大平均数。
public class AvgArray {
public static void main(String[] args) {
System.out.println(findMaxAverage(new int[]{1,12,-5,-6,50,3},4));
}
private static double findMaxAverage(int[] nums, int k) {
int sum = 0 ;
int n = nums.length ;
//先统计第一个窗口的和
for (int i = 0; i < k; i++) {
sum += nums[i] ;
}
int max = sum ;
for (int i = k; i < n; i++) {
sum = sum - nums[i - k] + nums[i] ;
max = Math.max(sum,max) ;
}
return 1.0 * max / k ;
}
}
二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
深度优先、广度优先
package nuc.zy.edu.day4;
import java.util.LinkedList;
import java.util.Queue;
/**
* @Author:皮皮
* @Date:2021/5/24 14:32
*/
//二叉树的最小深度 给定一个二叉树,找出其最小深度。 深度优先 将叶结点看成1开始 直到根节点
public class TreeDeep {
public static void main(String[] args) {
TreeNode node7 = new TreeNode(7, null, null);
TreeNode node6 = new TreeNode(6, node7, null);
TreeNode node5 = new TreeNode(5, null, null);
TreeNode node4 = new TreeNode(4, null, null);
TreeNode node3 = new TreeNode(3, node6, null);
TreeNode node2 = new TreeNode(2, node4, node5);
TreeNode node1 = new TreeNode(1, node2, node3);
System.out.println(minDepth(node1));
System.out.println(minDepth1(node1));
}
//深度优先 时间复杂度O(n)
private static int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
if (root.left == null && root.right == null) {
return 1;
}
int min = Integer.MAX_VALUE;
if (root.left != null) {
min = Math.min(minDepth(root.left), min);
}
if (root.right != null) {
min = Math.min(minDepth(root.right), min);
}
return min + 1; //每循环一层 深度+1
}
//广度优先 时间复杂度O(n)
private static int minDepth1(TreeNode root) {
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new LinkedList() ; //先进先出的队列
root.deep = 1 ;
queue.offer(root) ; //去在TreeNode中加一个变量属性deep 保存深度
//队列不为空 推出循环
while (!queue.isEmpty()) {
TreeNode node = queue.poll() ;
if (node.left == null && node.right == null) {
return node.deep ;
}
if (node.left != null) {
node.left.deep = node.deep + 1 ;
queue.offer(node.left) ;
}
if (node.right != null) {
node.right.deep = node.deep + 1 ;
queue.offer(node.right) ;
}
}
return 0;
}
static class TreeNode {
int val;
TreeNode left;
TreeNode right;
int deep ;
public TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
}
最长连续递增序列
给定一个未经排序的整数数组,找到最长且连续递增的子序列,并返回该序列的长度。
序列的下标是连续的
package nuc.zy.edu.day4;
/**
* @Author:皮皮
* @Date:2021/5/24 15:07
*/
//最长连续递增序列 给定一个未经排序的整数数组,找到最长且连续递增的子序列,并返回该序列的长度。
public class MaxSeq {
public static void main(String[] args) {
System.out.println(findLength(new int[]{1,2,3,2,3,4,3,4,5,6,7}));
// System.out.println(findLength(new int[]{1}));
}
private static int findLength(int[] nums) {
if (nums.length == 1) {
return 1 ;
}
int start = 0 ;
// int max = Integer.MIN_VALUE ;
int max = 0 ;
for (int i = 1; i < nums.length; i++) {
if (nums[i] <= nums[i - 1]) {
start = i ;
}
max = Math.max(max,i - start + 1) ;
}
return max ;
}
}
柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为5美元。顾客排队购买你的产品,一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付5美元、10美元或20美元。必须给每个顾客正确找零
注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零,返回 true ,否则返回false。
package nuc.zy.edu.day4;
/**
* @Author:皮皮
* @Date:2021/5/24 17:36
*/
//柠檬水找零 //在柠檬水摊上,每一杯柠檬水的售价为5美元。顾客排队购买你的产品,一次购买一杯。
//每位顾客只买一杯柠檬水,然后向你付5美元、10美元或20美元。必须给每个顾客正确找零
public class LemonChange {
public static void main(String[] args) {
System.out.println(change(new int[]{5,5,20}));
}
private static boolean change(int[] bills) {
int five = 0,ten = 0 ;
for (int bill : bills) {
if (bill == 5) {
five++ ;
} else if (bill == 10) {
if (five == 0) {
return false ;
}
five-- ;
ten++ ;
} else { //20
if(five > 0 && ten > 0) {
five-- ;
ten-- ;
} else if (five >= 3) {
five -= 3 ;
} else {
return false ;
}
}
}
//循环结束 未返回false就是true
return true ;
}
}
三角形的最大周长
给定由一些正数(代表长度)组成的数组arr,返回由其中三个长度组成的、面积不为零的三角形的最大周长。
如果不能形成任何面积不为零的三角形,返回0。
package nuc.zy.edu.day4;
import java.util.Arrays;
/**
* @Author:皮皮
* @Date:2021/5/24 17:52
*/
// 三角形的最大周长 给定由一些正数(代表长度)组成的数组arr,返回由其中三个长度组成的、面积不为零的三角形的最大周长。
public class Triangles {
public static void main(String[] args) {
System.out.println(largestPerimeter(new int[]{3,6,2,3}));
}
private static int largestPerimeter(int[] arr) {
Arrays.sort(arr) ;
for (int i = arr.length - 1; i >= 2; i--) {
//两边之和大于第三边 往前找 排序后arr[i]/前两个加起来都没有
if (arr[i - 1] + arr[i - 2] > arr[i]) {
return arr[i - 1] + arr[i - 2] + arr[i] ;
}
}
return 0 ;
}
}
二叉树遍历
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
层序遍历:从上往下、从左往右
递归遍历:使用递归方法遍历
迭代遍历:使用迭代方法实现递归函数与递归等价
morris遍历
递归遍历:使用递归方法遍历
package nuc.zy.edu.day5;
import nuc.zy.edu.day4.TreeDeep;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Stack;
/**
* @Author:皮皮
* @Date:2021/5/24 20:40
*/
//二叉树遍历
//前序遍历:根左右
//中序遍历:左根右
//后序遍历:左右根
//层序遍历:从上往下、从左往右
//递归遍历:使用递归方法遍历
public class BinaryTree {
static class TreeNode {
int val;
TreeNode left;
TreeNode right;
int deep ;
public TreeNode() {
}
public TreeNode(int val) {
this.val = val;
}
public TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
public static void main(String[] args) {
TreeNode node7 = new TreeNode(7,null,null) ;
TreeNode node6 = new TreeNode(6,null,null) ;
TreeNode node5= new TreeNode(5,node6,node7) ;
TreeNode node4 = new TreeNode(4,null,null) ;
TreeNode node3 = new TreeNode(3,null,null) ;
TreeNode node2 = new TreeNode(2,node4,node5) ;
TreeNode node1 = new TreeNode(1,node2,node3) ;
preOrder(node1);
System.out.println("-----------------");
midOrder(node1);
System.out.println("-----------------");
afterOrder(node1);
System.out.println("-----------------");
ArrayList list = new ArrayList() ;
levelOrder(node1,1,list);
System.out.println(Arrays.toString(list.toArray()));
}
//前序遍历----递归
private static void preOrder(TreeNode root) {
if (root == null) {
return;
}
System.out.println(root.val); //前序:第一次称为栈顶元素即打印
preOrder(root.left);
preOrder(root.right);
}
//中序遍历----递归
private static void midOrder(TreeNode root) {
if (root == null) {
return;
}
midOrder(root.left);
System.out.println(root.val); //中序:第二次称为栈顶元素即打印
midOrder(root.right);
}
//后序遍历----递归
private static void afterOrder(TreeNode root) {
if (root == null) {
return;
}
afterOrder(root.left);
afterOrder(root.right);
System.out.println(root.val); //后序:第三次称为栈顶元素即打印
}
//层序遍历----递归
private static void levelOrder(TreeNode root, int i, ArrayList list) {
if (root == null) {
return;
}
int length = list.size() ;
if (length <= i) { //防止下标越界
for (int j = 0; j <= i - length; j++) {
list.add(length + j,null) ;
}
}
list.set(i,root.val) ; //用set是因为防止扩容动态后移 add扩容会动态后移 set设置位置的值 add往后移一位
levelOrder(root.left,2 * i,list);
levelOrder(root.right,2 * i + 1,list);
}
}
迭代遍历:使用迭代方法实现递归函数与递归等价
package nuc.zy.edu.day5;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
/**
* @Author:皮皮
* @Date:2021/5/24 21:24
*/
//二叉树遍历
//前序遍历:根左右
//中序遍历:左根右
//后序遍历:左右根
//层序遍历:从上往下、从左往右
//迭代遍历:使用迭代方法实现递归函数与递归等价
public class BinaryTree2 {
static class TreeNode {
int val;
TreeNode left;
TreeNode right;
int deep;
public TreeNode() {
}
public TreeNode(int val) {
this.val = val;
}
public TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
public static void main(String[] args) {
TreeNode node7 = new TreeNode(7, null, null);
TreeNode node6 = new TreeNode(6, null, null);
TreeNode node5 = new TreeNode(5, node6, node7);
TreeNode node4 = new TreeNode(4, null, null);
TreeNode node3 = new TreeNode(3, null, null);
TreeNode node2 = new TreeNode(2, node4, node5);
TreeNode node1 = new TreeNode(1, node2, node3);
preOrder(node1);
System.out.println("-------------------");
midOrder(node1);
System.out.println("-------------------");
afterOrder(node1);
System.out.println("-------------------");
levelOrder(node1) ;
}
//前序遍历----迭代 栈 先进后出
private static void preOrder(TreeNode root) {
if (root != null) {
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.add(root);
while (!stack.isEmpty()) {
root = stack.pop();
if (root != null) {
System.out.println(root.val);
stack.push(root.right);
stack.push(root.left);
}
}
}
}
//中序遍历----迭代 栈 先进后出
private static void midOrder(TreeNode root) {
if (root != null) {
Stack<TreeNode> stack = new Stack<TreeNode>();
while (!stack.isEmpty() || root != null) {
if (root != null) {
stack.push(root);
root = root.left;
} else {
root = stack.pop();
System.out.println(root.val);
root = root.right;
}
}
}
}
//后序遍历----迭代 栈 先进后出
private static void afterOrder(TreeNode root) {
if (root != null) {
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode prev = null;
while (!stack.isEmpty() || root != null) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
if (root.right == null || root.right == prev) {
System.out.println(root.val);
prev = root;
root = null; //不改为null 死循环
} else {
stack.push(root);
root = root.right;
}
}
}
}
//层序遍历----迭代 队列 先进先出
private static void levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node != null) {
System.out.println(node.val);
queue.add(node.left);
queue.add(node.right);
}
}
}
}
morris遍历
package nuc.zy.edu.day5;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @Author:皮皮
* @Date:2021/5/25 8:42
*/
//二叉树遍历
//前序遍历:根左右
//中序遍历:左根右
//后序遍历:左右根
//层序遍历:从上往下、从左往右
//morris遍历
public class MorrisBinaryTree {
static class TreeNode {
int val;
TreeNode left;
TreeNode right;
int deep;
public TreeNode() {
}
public TreeNode(int val) {
this.val = val;
}
public TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
public static void main(String[] args) {
TreeNode node7 = new TreeNode(7, null, null);
TreeNode node6 = new TreeNode(6, null, null);
TreeNode node5 = new TreeNode(5, node6, node7);
TreeNode node4 = new TreeNode(4, null, null);
TreeNode node3 = new TreeNode(3, null, null);
TreeNode node2 = new TreeNode(2, node4, node5);
TreeNode node1 = new TreeNode(1, node2, node3);
preOrder(node1);
System.out.println("-----------------");
midOrder(node1);
System.out.println("-----------------");
afterOrder(node1);
}
}
//前序遍历----morris遍历
private static void preOrder(TreeNode cur) {
if (cur == null) {
return;
}
TreeNode mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) { //建立线索指针
mostRight.right = cur;
System.out.println(cur.val);
cur = cur.left;
continue;
} else { //mostRight.right == cur 删除线索指针
mostRight.right = null;
}
} else {
System.out.println(cur.val);
}
cur = cur.right;
}
}
//中序遍历----morris遍历
private static void midOrder(TreeNode cur) {
if (cur == null) {
return;
}
TreeNode mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) { //建立线索指针
mostRight.right = cur;
cur = cur.left;
continue;
} else { //mostRight.right == cur 删除线索指针
mostRight.right = null;
}
}
System.out.println(cur.val);
cur = cur.right;
}
}
//后序遍历----morris遍历
private static void afterOrder(TreeNode cur) {
if (cur == null) {
return;
}
TreeNode root = cur ;
TreeNode mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) { //建立线索指针
mostRight.right = cur;
cur = cur.left;
continue;
} else { //mostRight.right == cur 删除线索指针
mostRight.right = null;
print(cur.left);
}
}
cur = cur.right;
}
print(root);
}
private static void print(TreeNode head) {
TreeNode tail = reverse(head) ;
while (tail != null) {
System.out.println(tail.val);
tail = tail.right ;
}
reverse(head) ;
}
private static TreeNode reverse(TreeNode head) {
TreeNode prev = null,curr,next ;
curr = head ;
while (curr != null) {
next = curr.right ;
curr.right = prev ;
prev = curr ;
curr = next ;
}
return prev ;
}
}
省份数量
有n个城市,其中一些彼此相连,另一些没有相连。如果城市a与城市b直接相连,且城市b与城市c直接相连,那么城市a与城市c间接相连。
省份是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个n×n的矩阵isConnected,其中 isConnected[i][j] =1表示第i个城市和第j个城市直接相连,而isConnected[i][j]=0表示二者不直接相连。
返回矩阵中省份的数量。
扩展:朋友圈问题,亲戚问题
package nuc.zy.edu.day6;
import java.util.LinkedList;
import java.util.Queue;
/**
* @Author:皮皮
* @Date:2021/5/25 10:15
*/
//省份数量
//有n个城市,其中一些彼此相连,另一些没有相连。如果城市a与城市b直接相连,且城市b与城市c直接相连,那么城市a与城市c间接相连。
//省份是一组直接或间接相连的城市,组内不含其他没有相连的城市。
//给你一个n×n的矩阵isConnected,其中 isConnected[i][j] =1表示第i个城市和第j个城市直接相连,而isConnected[i][j]=0表示二者不直接相连。
//返回矩阵中省份的数量。
//扩展:朋友圈问题,亲戚问题
public class GroupMerge {
public static void main(String[] args) {
System.out.println(getProvince(new int[][]{{1,1,0},{1,1,0},{0,0,1}})); //2
System.out.println(getProvince(new int[][]{{1,0,0},{0,1,0},{0,0,1}})); //3
System.out.println("----------------");
System.out.println(getProvinceBfs(new int[][]{{1,1,0},{1,1,0},{0,0,1}})); //2
System.out.println(getProvinceBfs(new int[][]{{1,0,0},{0,1,0},{0,0,1}})); //3
System.out.println("----------------");
System.out.println(mergeFind(new int[][]{{1,1,0},{1,1,0},{0,0,1}})); //2
System.out.println(getProvinceBfs(new int[][]{{1,0,0},{0,1,0},{0,0,1}})); //3
// System.out.println(mergeFind(new int[][]{{1,1,0 ,1},{1,1,0,0},{0,0,1,0},{1,0,0,1}})); //2
}
//深度优先 时间复杂度O(n²) 空间复杂度O(2n)---->O(n)
private static int getProvince(int[][] cities) {
int cityNum = cities.length;
boolean[] visited = new boolean[cityNum] ;
int provinces = 0 ;//计数器
for (int i = 0; i < cityNum; i++) {
if (!visited[i]) {
//深度优先
//二维数组的第i个元素 二维n×n不变第i个元素城市的数量还是cityNum(n)
//visited打标机看第i个元素城市是否被访问过 cities 关联关系看城市是否关联在一起 第i个元素城市cities[i][x]
dfs(i,cityNum,visited,cities) ;
provinces++ ;
}
}
return provinces;
}
private static void dfs(int i, int cityNum, boolean[] visited, int[][] cities) {
for (int j = 0; j < cityNum; j++) {
if (cities[i][j] == 1 && !visited[j]) {
visited[j] = true ;
//递归调用 例如:1-2-3 循环遍历的上边第i个元素的关系1-2在1中 而2-3关系在2中 所以要递归
dfs(j, cityNum,visited,cities) ;
}
}
}
//广度优先
private static int getProvinceBfs(int[][] cities) {
int cityNum = cities.length;
boolean[] visited = new boolean[cityNum] ;
int provinces = 0 ;//计数器
Queue<Integer> queue = new LinkedList<Integer>() ;
for (int i = 0; i < cityNum; i++) {
//判断该元素是否被打标记被省份包含过
if (!visited[i]) {
//元素下标入队
queue.offer(i) ;
//广度优先 while循环 每个元素向外圈一层一层的寻找每层的元素 并打上标记
while (!queue.isEmpty()) {
//取出第i个元素
int poll = queue.poll();
//打标记
visited[poll] = true ;
//循环二维数组第i个元素内其他城市是否与第i个元素关联
for (int j = 0; j < cityNum; j++) {
//城市之间是否关联 并且未被打标记
if (cities[i][j] == 1 && !visited[j]) {
//元素外层符合条件元素全部入队
queue.offer(j) ;
}
}
}
provinces++ ;
}
}
return provinces;
}
//并查集
private static int mergeFind(int[][] cities) {
int cityNum = cities.length ;
//通过下标表示存放哪个元素的根节点
int[] head = new int[cityNum] ;
//通过下标表示存放哪个元素的树高
int[] level = new int[cityNum] ;
for (int i = 0; i < cityNum; i++) {
//初始化
//先将每个城市的head结点指向自己
head[i] = i ;
//每个城市的树高是1
level[i] = 1 ;
}
//双重循环 判断城市i与j之间是否存在关联关系 如果存在则合并
for (int i = 0; i < cityNum; i++) {
for (int j = i + 1; j < cityNum; j++) {
if (cities[i][j] == 1) {
merge(i,j,head,level);
}
}
}
int count = 0 ;
for (int i = 0; i < head.length; i++) {
if (head[i] == i) {
count++ ;
}
}
return count ;
}
//两个树或者结点进行合并
static void merge(int x,int y,int[] head,int[] level) {
//x下标的根节点 用来找根节点
int i = find(x,head) ;
//y下标的根节点 用来找根节点
int j = find(y,head) ;
//一个结点一个树
if (i == j) {
return;
}
//树高小的树向树高大的树进行合并 如果两个树高度相等 合并后原树高加一
if (level[i] <= level[j]) {
head[i] = j ;
//如果两个树高度相等 合并后原树高加一 与下边的if == 二选一
// if (level[i] == level[j]) {
// level[j]++ ;
// }
} else {
head[j] = i ;
}
//如果两个树高度相等 合并后原树高加一
if (level[i] == level[j]) {
level[i]++ ;
level[j]++ ;
}
}
//用来找根节点 如果数组下标不跟元素相等向前递归直至相等,相等时元素即就是根
private static int find(int x, int[] head) {
//x下标的元素时自己即就是一个根(本身是一个树)
if (head[x] == x) {
return x ;
}
//将递归元素的值 根 改成head[x]
head[x] = find(head[x],head) ;
return head[x] ;
}
}
预测赢家
给定一个表示分数的非负整数数组。玩家1从数组任意一端拿取一个分数,随后玩家2继续从剩余数组任意一端拿取分数,然后玩家1拿,……。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。
给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。
扩展:石子游戏
package nuc.zy.edu.day6;
import java.util.Arrays;
/**
* @Author:皮皮
* @Date:2021/5/26 16:18
*/
//预测赢家
//给定一个表示分数的非负整数数组。玩家1从数组任意一端拿取一个分数,随后玩家2继续从剩余数组任意一端拿取分数,然后玩家1拿,……。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。
//给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。
//扩展:石子游戏
public class CanWin {
//l r l == r arr[l]
//lr r - l == 1 max(arr[l],arr[r])
//递归函数的作用:统计分值 出口 范围递减的等式
//如果我选l--->arr[l]
//对手也会判断选l还是r哪个大
//假如选l 留下的区间是 l + 2,r
//假如选r 留下的区间是 l + 1,r - 1
//然后我又会在这两个区间去分别求区间内的最大值去判断哪个值选择一个比较大的数
public static void main(String[] args) {
int[] arr = new int[]{5,200,2,3} ;
// int[] arr = new int[]{5,200,2,3,5} ;
int sum = 0 ;
for (int i : arr) {
sum += i ;
}
int p1 = maxScore(arr,0,arr.length - 1) ;
System.out.println(p1 > sum - p1);
System.out.println("---------------");
System.out.println(maxScore1(arr,0,arr.length - 1) > 0 ? true : false);
System.out.println("---------------");
System.out.println(dp(arr));
}
//递归
static int maxScore(int[] arr,int l,int r) {
if (l == r) {
return arr[l] ;
}
int sLeft = 0 ,sRight = 0 ;
if (r - l == 1) {
sLeft = arr[l] ;
sRight = arr[r] ;
}
if (r - l >= 2){
int num = maxScore(arr,l + 1,r - 1) ;
//sLeft若我第一个取左边的值Math.min他留给我最小值 而我要在两个最小值中取最大值
// sLeft = arr[l] + Math.min(maxScore(arr,l + 2,r),maxScore(arr,l + 1,r - 1)) ;
sLeft = arr[l] + Math.min(maxScore(arr,l + 2,r),num) ;
// sRight= arr[r] + Math.min(maxScore(arr,l + 1,r - 1),maxScore(arr,l,r - 2)) ;
sRight= arr[r] + Math.min(num,maxScore(arr,l,r - 2)) ;
}
return Math.max(sLeft,sRight) ;
}
//递归-----优化 减少了递归的次数 最终比较差值
static int maxScore1(int[] arr,int l,int r) {
if (l == r) {
return arr[l] ;
}
int sLeft = arr[l] - maxScore1(arr,l + 1,r) ;
int sRight = arr[r] - maxScore1(arr,l,r - 1) ;
return Math.max(sLeft,sRight) ;
}
//动态规划 将maxScore1(arr,l + 1,r)存储到二维数组[i+1][j] [i][j-1] dp数组
//初始化
static boolean dp(int[] arr){
int length = arr.length ;
int[] dp = new int[length] ;
for (int i = 0; i < length; i++) {
dp[i] = arr[i] ;
}
for (int i = length - 2; i >= 0 ; i--) {
for (int j = i + 1; j < length ; j++) {
dp[j] = Math.max(arr[i] - dp[j],arr[j] - dp[j - 1]) ;
}
}
System.out.println(Arrays.toString(dp));
return dp[length - 1] >= 0 ;
// int[][] dp = new int[length][length] ;
// for (int i = 0; i < length; i++) {
// dp[i][i] = arr[i] ;
// }
// for (int i = length - 2; i >= 0 ; i--) {
// for (int j = i + 1; j < length ; j++) {
// dp[i][j] = Math.max(arr[i] - dp[i + 1][j],arr[j] - dp[i][j - 1]) ;
// }
// }
// return dp[0][length - 1] >= 0 ;
}
}
香槟塔
把玻璃杯摆成金字塔的形状,其中第一层有1个玻璃杯,第二层有2个,依次类推到第100层。
从顶层的第一个玻璃杯开始倾倒一些香槟,当顶层的杯子满了,任何溢出的香槟都会立刻等流量的流向左右两侧的玻璃杯。当左石两边的杯子也满了,就会等流量的流向它们左右两边的杯子,依次类推。(当最底层的玻璃杯满了,香槟会流到地板上)
例如,在倾倒一杯香槟后,最顶层的玻璃杯满了。倾倒了两杯香槟后,第二层的两个玻璃杯各自盛放一半的香槟。在倒三杯香槟后,第二层的香槟满了–此时总共有三个满的玻璃杯。在倒第四杯后,第三层中间的玻璃杯盛放了一半的香槟,他两边的玻璃杯各自盛放了四分之一的香槟
现在当倾倒了非负整数杯香槟后,返回第i行j个玻璃杯所盛放的香槟占玻璃杯容积的比例(i和j都从0开始)
package nuc.zy.edu.day7;
/**
* @Author:皮皮
* @Date:2021/5/27 10:17
*/
//香槟塔
//把玻璃杯摆成金字塔的形状,其中第一层有1个玻璃杯,第二层有2个,依次类推到第100层。
//从顶层的第一个玻璃杯开始倾倒一些香槟,当顶层的杯子满了,任何溢出的香槟都会立刻等流量的流向左右两侧的玻璃杯。当左石两边的杯子也满了,就会等流量的流向它们左右两边的杯子,依次类推。(当最底层的玻璃杯满了,香槟会流到地板上)
//例如,在倾倒一杯香槟后,最顶层的玻璃杯满了。倾倒了两杯香槟后,第二层的两个玻璃杯各自盛放一半的香槟。在倒三杯香槟后,第二层的香槟满了–此时总共有三个满的玻璃杯。在倒第四杯后,第三层中间的玻璃杯盛放了一半的香槟,他两边的玻璃杯各自盛放了四分之一的香槟
//现在当倾倒了非负整数杯香槟后,返回第i行j个玻璃杯所盛放的香槟占玻璃杯容积的比例(i和j都从0开始)
public class ChampagneTower {
public static void main(String[] args) {
System.out.println(champagneTower(5,2,1));
System.out.println(champagneTower(5,2,0));
}
private static double champagneTower(int poured, int query_row, int query_glass) {
double[][] ca = new double[100][100] ;
//初始化最开始杯子的容量
ca[0][0] = poured ;
//题目i,j都是从0开始
for (int r = 0; r <= query_row; r++) {
//第i层有i个杯子
for (int l = 0; l <= r; l++) {
//杯子满后,各给下面两侧一层的杯子分一半
double d = (ca[r][l] - 1.0) / 2 ;
if (d > 0) {
ca[r + 1][l] += d;
ca[r + 1][l + 1] += d ;
}
}
}
//满后返回1,不满返回最小值
return Math.min(ca[query_row][query_glass],1);
}
}
井字游戏
用字符串数组作为井字游戏的游戏板board,判断该游戏板有没有可能最终形成
游戏板是一个3x3数组,由字符"",“X"和"O"组成。字符”"代表一个空位。两个玩家轮流将字符放入空位,一个玩家执X棋,另一个玩家执O棋
“X”和“O”只允许放置在空位中,不允许对已放有字符的位置进行填充。当有3个相同(且非空)的字符填充任何行、列或对角线时,游戏结束,board生成
package nuc.zy.edu.day7;
import com.sun.org.apache.regexp.internal.RE;
/**
* @Author:皮皮
* @Date:2021/5/27 11:11
*/
//井字游戏
//用字符串数组作为井字游戏的游戏板board,判断该游戏板有没有可能最终形成
//游戏板是一个3x3数组,由字符"","X"和"O"组成。字符""代表一个空位。两个玩家轮流将字符放入空位,一个玩家执X棋,另一个玩家执O棋
//“X”和“O”只允许放置在空位中,不允许对已放有字符的位置进行填充。当有3个相同(且非空)的字符填充任何行、列或对角线时,游戏结束,board生成
public class TicTacToe {
public static void main(String[] args) {
System.out.println(validBoard(new String[]{"XXX", "OXO", "O O"}));
System.out.println("----------------");
System.out.println(validBoard(new String[]{"XXX", " XO", "O O"}));
}
public static boolean validBoard(String[] board) {
//X赢了 X - O = 1
//O赢了 O - X = 0
//胜负未分 X - O = 1 O - X = 0
int xCount = 0, oCount = 0;
for (String row : board) {
for (char c : row.toCharArray()) {
if (c == 'X') {
xCount++;
}
if (c == 'O') {
oCount++;
}
}
}
if (xCount != oCount && xCount - oCount != 1) {
return false ;
}
//时间复杂度 O(1)
//可以修改 减少空间复杂度 win(String[] board,String flag) ---> win(String[] board,char flag)
if (win(board,"XXX") && xCount - oCount != 1) {
return false ;
}
if (win(board,"OOO") && xCount - oCount != 0) {
return false ;
}
return true ;
}
static boolean win(String[] board,String flag){
for (int i = 0; i < 3; i++) {
//纵向3连
if (flag.equals("" + board[0].charAt(i) + board[1].charAt(i) + board[2].charAt(i))) {
return true ;
}
//横向3连
if (flag.equals(board[i])) {
return true ;
}
}
// \对角线
if (flag.equals("" + board[0].charAt(0) + board[1].charAt(1) + board[2].charAt(2))) {
return true ;
}
// /对角线
if (flag.equals("" + board[0].charAt(2) + board[1].charAt(1) + board[2].charAt(0))) {
return true ;
}
return false ;
}
}
String Search
给定两个字符串A、B,判断B在A中是否存在,存在返回A中的下标,不存在返回-1
例:
A: ABCABCAABCABCD
B: ABCABCD
返回值:7
package nuc.zy.edu.day7;
import javax.print.DocFlavor;
import java.beans.beancontext.BeanContext;
import java.util.Arrays;
/**
* @Author:皮皮
* @Date:2021/5/27 15:08
*/
//String Search
//给定两个字符串A、B,判断B在A中是否存在,存在返回A中的下标,不存在返回-1
//>例:
//> A: ABCABCAABCABCD
//> B: ABCABCD
//>返回值:7
public class Kmp {
public static void main(String[] args) {
String str1 = "ABCABCAABCABCD" ;
String strPattern = "ABCABCD" ;
int[] next = new int[strPattern.length()] ;
getNext(strPattern.toCharArray(), next);
int i = search(str1.toCharArray(),strPattern.toCharArray(),next) ;
System.out.println(Arrays.toString(next));
System.out.println(i);
System.out.println("----------------");
System.out.println(str1.indexOf(strPattern));
}
static int search(char[] str,char[] pattern ,int[] next) {
int i = 0 ;
int j = 0 ;
while (i < str.length && j < pattern.length) {
if (j == -1 || str[i] == pattern[j]) {
i++ ;
j++ ;
}else {
j = next[j] ;
}
}
if (j == pattern.length) {
return i - j ;
} else {
return -1 ;
}
}
static void getNext(char[] pattern ,int[] next){
next[0] = -1 ;
int i = 0,j = -1 ;
while (i < pattern.length) {
if (j == -1) {
i++ ;
j++ ;
} else if (pattern[i] == pattern[j]) {
i++ ;
j++ ;
next[i] = j ;
} else {
j = next[j] ;
}
}
}
}
打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
输入:[1,2,3,1]输出:4 输入:[2,7,9,3,1]输出:12
升级版一,升级版二(街道,环型)
package nuc.zy.edu.day8;
/**
* @Author:皮皮
* @Date:2021/5/28 8:53
*/
//打家劫舍
//你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
//给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
//>输入:[1,2,3,1]输出:4 输入:[2,7,9,3,1]输出:12
public class Rob {
public static void main(String[] args) {
int[] nums = new int[]{1,2,3,1} ;
System.out.println(maxMoney(nums,nums.length - 1));
System.out.println(maxMoney1(nums));
System.out.println(Math.max(maxMoney2(nums,0,nums.length - 2),maxMoney2(nums,1,nums.length - 1)));
System.out.println("-----------------");
nums = new int[]{2,7,9,3,1} ;
System.out.println(maxMoney(nums,nums.length - 1));
System.out.println(maxMoney1(nums));
System.out.println(Math.max(maxMoney2(nums,0,nums.length - 2),maxMoney2(nums,1,nums.length - 1)));
System.out.println("-----------------");
nums = new int[]{9,2} ;
System.out.println(maxMoney(nums,nums.length - 1));
System.out.println(maxMoney1(nums));
System.out.println(Math.max(maxMoney2(nums,0,nums.length - 2),maxMoney2(nums,1,nums.length - 1)));
}
//递归 房屋是首尾不相连 直线形式
static int maxMoney(int[] nums,int index){
//index是递归会变化的下标
if (nums == null || index < 0) {
return 0 ;
}
if (index == 0) {
return nums[0] ;
}
//没有必要写index == 1 递归遍历会遍历到maxMoney(nums,0)<nums[0]> ,maxMoney(nums,-1)<0> + nums[1]
return Math.max(maxMoney(nums,index - 1), maxMoney(nums,index - 2) + nums[index]) ;
}
//房屋是首尾不相连 直线形式
//动态规划 最优子结构 n->n-1 递推公式 重叠子问题
static int maxMoney1(int[] nums){
int length = nums.length ;
if (nums == null || length == 0) {
return 0 ;
}
if (length == 0) {
return nums[0] ;
}
//空间复杂度O(n)
// int[] dp = new int[nums.length] ;
// dp[0] = nums[0] ;
// dp[1] = Math.max(nums[0],nums[1]) ;
// for (int i = 2; i < length; i++) {
// dp[i] = Math.max(dp[i - 1] ,dp[i - 2] + nums[i]) ;
// }
//优化 空间复杂度O(1)
int first = nums[0] ,second = nums[1] ;
//两个选最大的
if (length == 2) {
second = Math.max(first,second) ;
}
for (int i = 2; i < length; i++) {
int temp = second ;
second = Math.max(first + nums[i],second) ;
first = temp ;
}
return second ;
}
//升级版题2 结果不一样
//房屋是首尾相连 环型形式 同上述动态规划一样 不取首部或者尾部又可以使用上述方法进行计算
static int maxMoney2(int[] nums,int start,int end){
if (nums.length == 2) {
return Math.max(nums[0],nums[1]) ;
}
// int first = nums[start] ,second = Math.max(nums[start],nums[start + 1]) ;
int first = nums[start] ,second = nums[start + 1] ;
for (int i = start + 2; i <= end; i++) {
int temp = second ;
second = Math.max(first + nums[i],second) ;
first = temp ;
}
return second ;
}
}
升级版三 — 打家劫舍3
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
package nuc.zy.edu.day8;
/**
* @Author:皮皮
* @Date:2021/5/28 10:46
*/
//升级版三 --- 打家劫舍3
//在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
//计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
public class Rob1 {
static class TreeNode {
int val;
TreeNode left;
TreeNode right;
int deep ;
public TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
public static void main(String[] args) {
TreeNode node5 = new TreeNode(1,null,null) ;
TreeNode node4 = new TreeNode(3,null,null) ;
TreeNode node3 = new TreeNode(3,null,node5) ;
TreeNode node2 = new TreeNode(2,null,node4) ;
TreeNode node1 = new TreeNode(3,node2,node3) ;
int[] dfs = dfs(node1);
System.out.println(Math.max(dfs[0], dfs[1]));
}
//动态规划 递归
public static int[] dfs(TreeNode node) {
//选与不选当前结点的最优解 int[]{select最优解,notSelect最优解}
if (node == null) {
return new int[]{0,0} ;
}
//递归
int[] l = dfs(node.left) ;
int[] r = dfs(node.right) ;
//当前结点偷 子节点不偷的和
int select = node.val + l[1] + r[1] ;
//当前结点不偷--->子节点偷与不偷二选一的最大值相加和 判断左右结点分别偷与不偷的最大值相加
int notSelect = Math.max(l[0],l[1]) + Math.max(r[0],r[1]) ;
return new int[] {select,notSelect} ;
}
}
Dota2参议院
Dota2的世界里有两个阵营:Radiant(天辉)和Dire(夜魇)
Dota2参议院由来自两派的参议员组成。现在参议院希望对一个Dota2游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的一项:
禁止一名参议员的权利:参议员可以让另一位参议员在这一轮和随后的几轮中丧失所有的权利。
宣布胜利:如果参议员发现有权利投票的参议员都是同一个阵营的,他可以宣布胜利并决定在游戏中的有关变化。
给定一个字符串代表每个参议员的阵营。字母“R”和“D”分别代表了Radiant(天辉)和Dire(夜魇)。然后,如果有n个参议员,给定字符串的大小将是n
以轮为基础的过程从给定顺序的第一个参议员开始到最后一个参议员结束。这一过程将持续到投票结束。所有失去权利的参议员将在过程中被跳过。
假设每一位参议员都足够聪明,会为自己的政党做出最好的策略,你需要预测哪一方最终会宣利并在Dota2游戏中决定改变。输出应该是Radiant或Dire
package nuc.zy.edu.day8;
import java.util.LinkedList;
import java.util.Queue;
/**
* @Author:皮皮
* @Date:2021/5/28 17:22
*/
//Dota2参议院
//Dota2的世界里有两个阵营:Radiant(天辉)和Dire(夜魇)
//Dota2参议院由来自两派的参议员组成。现在参议院希望对一个Dota2游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的一项:
//禁止一名参议员的权利:参议员可以让另一位参议员在这一轮和随后的几轮中丧失所有的权利。
//宣布胜利:如果参议员发现有权利投票的参议员都是同一个阵营的,他可以宣布胜利并决定在游戏中的有关变化。
//给定一个字符串代表每个参议员的阵营。字母“R”和“D”分别代表了Radiant(天辉)和Dire(夜魇)。然后,如果有n个参议员,给定字符串的大小将是n
//以轮为基础的过程从给定顺序的第一个参议员开始到最后一个参议员结束。这一过程将持续到投票结束。所有失去权利的参议员将在过程中被跳过。
//假设每一位参议员都足够聪明,会为自己的政党做出最好的策略,你需要预测哪一方最终会宣利并在Dota2游戏中决定改变。输出应该是Radiant或Dire
public class Dota2 {
public static void main(String[] args) {
System.out.println(predictPartyVictory("RDD")); //D
System.out.println(predictPartyVictory("RRDD")); //R
System.out.println(predictPartyVictory("DRRDD")); //D
System.out.println(predictPartyVictory("DRRRD")); //D
}
//时间复杂度O(n) 空间复杂度O(n)
public static String predictPartyVictory(String senate) {
//两个队列 分别存放R,D的议员 出队 入队
Queue<Integer> r = new LinkedList<Integer>() ;
Queue<Integer> d = new LinkedList<Integer>() ;
int length = senate.length() ;
//r,d队列循环遍历字符串存放R,D的议员
for (int i = 0; i < length; i++) {
if (senate.charAt(i) == 'R') {
r.offer(i) ;
}else {
d.offer(i) ;
}
}
while (!r.isEmpty() && !d.isEmpty()) {
int rPoll = r.poll(),dPoll = d.poll() ;
//比较下标指针 小的出队再重新入队
if (rPoll < dPoll) {
r.offer(rPoll + length) ; //让R进入下面一轮,不能干扰本轮
} else {
d.offer(dPoll + length) ;
}
}
return d.isEmpty() ? "R" : "D" ;
}
}
优势洗牌
给定两个大小相等的数组A和B,A相对于B的优势可以用满足A[i]>B[i]的索引i的数目来描述。
返回A的任意排列,使其相对于B的优势最大化。
package nuc.zy.edu.day8;
import java.util.*;
/**
* @Author:皮皮
* @Date:2021/5/28 17:57
*/
//优势洗牌
//给定两个大小相等的数组A和B,A相对于B的优势可以用满足A[i]>B[i]的索引i的数目来描述。
//返回A的任意排列,使其相对于B的优势最大化
public class HorseRacing {
public static void main(String[] args) {
int[] A = new int[]{10,24,8,32} ;
int[] B = new int[]{13,25,25,11} ;
System.out.println(Arrays.toString(advantageCount(A,B)));
}
//时间复杂度O(nlogn) 空间复杂度O(n)
private static int[] advantageCount(int[] A, int[] B) {
int[] sortB = B.clone();
//复制B将B排序
Arrays.sort(sortB) ;
Arrays.sort(A) ;
Map<Integer, Deque<Integer>> bMap = new HashMap<>() ;
for (int b : B) {
//将指定的值与该映射中的指定键相关联(可选操作)。
//如果映射先前包含了密钥的映射,则旧值将被指定的值替换。
//bMap.put(b,new LinkedList<>()) key值相同
bMap.put(b,new LinkedList<>()) ;
}
// System.out.println(bMap.toString()); //{25=[], 11=[], 13=[]}
//垃圾桶
Deque<Integer> aq = new LinkedList<>() ;
int j = 0 ;
for (int a : A) {
if (a > sortB[j]) {
//将a中的元素往Deque<Integer>队列中存bMap.get(25)存放了两个值
//bMap.get(sortB[j++])----->Deque<Integer>是一个队列 往队列中存值
bMap.get(sortB[j++]).add(a) ;
} else {
aq.add(a) ;
}
}
int[] ans = new int[A.length] ;
//传入的参数B未被排序
for (int i = 0; i < B.length; i++) {
//当循环遍历到B[i]相同值时还是从一个队列中取值
if (bMap.get(B[i]).size() > 0){
ans[i] = bMap.get(B[i]).removeLast() ;
} else {
ans[i] = aq.removeLast() ;
}
}
return ans ;
}
}