算法题模板

面试Tips

业务为主技巧为主
笔试55%45%笔试需要淘汰率,更考察经验,笔试卷面分过 35/100
面试30%70%技巧为主反而更考察基础(比如窗口最大值最小值、单调栈、动态规划斜率优化)

算法题模板

注意点:

  • 子串、子数组必须连续,子序列必须连续

Master公式

简介
在编程中,递归是非常常见的一种算法,由于代码简洁而应用广泛,但递归相比顺序执行或循环程序,时间复杂度难以计算,而master公式就是用于计算递归程序的时间复杂度。

公式
T(N) = a * T(N / b) + O(N ^ d)

b:子过程的样本量
a:子过程的计算次数
O(N ^ d):子结果合并的时间复杂度
满足如上公式的程序都可以根据master公式计算时间复杂度:

log(b,a) > d :时间复杂度为O(N ^ log(b,a))
log(b,a) = d :时间复杂度为O(N ^ d * log N)
log(b,a) < d :时间复杂度为O(N ^ d)


对数器 & 比较器

  • 对数器
public class Comparator {
    //生成随机数组
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random() - (int) ((maxValue + 1) * Math.random()));
        }
        return arr;
    }

    public static int[] copyArray(int[] arr) {
        if (arr == null) return null;
        return Arrays.copyOf(arr, arr.length);
    }
    
    //判断两个数组是否完全相同
    public static boolean isEquals(int[] arr1, int[] arr2) {
        if (arr1 == null && arr2 == null) {
            return true;
        }
         if (arr1 == null || arr2 == null) {
            return false;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    public static void comparator(){
        int maxSize = 50;
        int maxValue = 100;
        int testTime = 1000;
        boolean success = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr = generateRandomArray(maxSize, maxValue);
            int[] copy = copyArray(arr);
            Arrays.sort(copy);
            //TODO 待定排序策略
            if (! isEquals(arr, copy)) {
                success = false;
                break;
            }
        }
        System.out.println(success == true ? "perfect" : "totally wrong!!!");
    }

    public static void main(String[] args) {
        comparator();
    }
}
  • 比较器 : 重载运算符

一些知识

Map.entrySet

  • java中Map存放的是许多个键值对的数据,而Map.Entry数据类型可以想象成Map中的一个键值对的数据,可以存放一个键与一个值,显然一个Map对象可以看成存储了多个Map.Entry的数据组

  • 从Map中获取Map.Entry:
    通过Map.entrySet()方法可以从指定的map对象中获取一个Set集合,集合中的元素是Map.Entry类型

    //寻找数组的多数元素,出现次数>n/2
    public int majorityElement(int[] nums) {
        Map<Integer, Integer> counts = countNums(nums);
        
        //相当于是声明了一个键值对的对象,用于存放map中众数的key-value
        Map.Entry<Integer, Integer> majorityEntry = null;
        // map.entrySet  获得的是map中的每一个键值对 (将key-value看成时一个 Map.Entry对象)所构成的set集合
        //可以很方便的遍历同时获得key,value
        for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {
            if (majorityEntry == null || entry.getValue() > majorityEntry.getValue()) {
    
            }
        }
        for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {
            if (majorityEntry == null || entry.getValue() > majorityEntry.getValue()) {
                majorityEntry = entry;
            }
        }
    
        return majorityEntry.getKey();
    }
    private Map<Integer, Integer> countNums(int[] nums) {
        Map<Integer, Integer> counts = new HashMap<Integer, Integer>();
        for (int num : nums) {
            if (! counts.containsKey(num)) {
                counts.put(num, 1);
            } else {
                counts.put(num, counts.get(num) + 1);
            }
        }
        return counts;
    }
    

链表、二叉树

  • 链表和二叉树定义本身还有递归,因此经常可以使用递归来解答。

Set

  • set.add();方法

语法 boolean add(E e)

返回值:如果Set集合中不包含要添加的对象,则添加对象并返回true;否则返回false。

参数:e是要添加到Set集合中的对象。

Map

  • map.put();方法
	public V put(K key, V value) {        如果key不存在,插入成功,返回null;如果key存在,返回之前对应的value。
		return putVal(hash(key), key, value, false, true);
  • map的key为基本数据类型时为值传递(拷贝一份),引用数据类型时为内存地址,8字节

常见排序

快排

  • 可以将快排改造为小样本下使用插入排序,大样本下使用快排
  • Arrays.sort(); 方法 基本数据类型使用快排,引用数据类型使用归并排序,保证稳定性

插入排序

public static void insertionSort(int[] arr) {
    if (arr == null || arr.length < 2) return;
    for (int i = 1; i < arr.length; i++) {
        for (int j = i - 1; j >= 0 && arr[j + 1] < arr[j]; j--) {
            swap(arr[j], arr[j + 1], arr);
        }
    }
}

public static void swap(int i, int j, int[] arr) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

归并排序

public static void process(int[] arr, int l, int r) {
    if (l == r) return;
    int mid = l + ((l - r) >> 1);
    process(arr, l, mid);
    process(arr, mid + 1, r);
    merge(arr, l, mid, r);

}

private static void merge(int[] arr, int l, int mid, int r) {
    int[] help = new int[l + r + 1];
    int i = 0;
    int p1 = l;
    int p2 = mid + 1;
    while (p1 <= mid && p2 <= r) {
        help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    }
    while (p1 <= mid) {
        help[i++] = arr[p1++];
    }
    while (p2 <= r) {
        help[i++] = arr[p2++];
    }
    for (int j = 0; j < help.length; j++) {
        arr[l + j] = help[j];
    }
}

快速排序

public static void quickSort(int[] arr) {
    if (arr == null || arr.length < 2) return;
    quickSort(arr, 0, arr.length - 1);
}

private static void quickSort(int[] arr, int L, int R) {
    if (L < R) {
        swap(arr, L + (int) ((R - L + 1) * Math.random()), R);
        //划分区域
        int[] p = partition(arr, L, R);
        quickSort(arr, L, p[0] - 1);
        quickSort(arr, p[1] + 1, R);
    }
}

private static int[] partition(int[] arr, int L, int R) {
    int less = L - 1;
    int more = R;
    while (L < more) {
        if (arr[L] < arr[R]) {
            swap(arr, ++ less, L++);
        } else if (arr[L] > arr[R]) {
            swap(arr, -- more, L);
        } else {
            L++;
        }
    }
    swap(arr, more, R);
    return new int[]{less + 1, more};
}

private static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

堆排序

public static void heapSort(int[] arr) {
        if (arr == null || arr.length < 2) return;

        //相当于一个个给元素,建立大根堆
//        for (int i = 0; i < arr.length; i++) {
//            heapInsert(arr, i);
//        }
        //相当于一股脑给出整个堆,然后从最右侧叶节点进行调整
        for (int i = arr.length - 1; i >= 0; i--) {
            heapify(arr, i, arr.length);
        }

        int heapSize = arr.length;
        //0位置数与最后一位的数(n-1)交换,并让heapSize减一,表示从堆中移除该元素(也意味着该元素已经处于了正确的位置上了)
        swap(arr, 0, -- heapSize);
        while (heapSize > 0) {
            heapify(arr, 0, heapSize);  //将新换上来的0位置的数不断地heapify至该去的位置
            swap(arr, 0, -- heapSize);
        }
    }

    //相当于建堆,不断的与其父比较向上移动
    public static void heapInsert(int[] arr, int index) {
        while (arr[index] > arr[(index - 1) / 2]) {
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    //x在index位置,是否能往下移动
    public static void heapify(int[] arr, int index, int heapSize) {
        int left = 2 * index + 1;
        //意味着有孩子,有向下移动的可能性
        while (left < heapSize) {
            //选出较大的孩子
            int largest = left + 1 < heapSize && arr[left + 1] > arr[left]
                    ? left + 1 : left;
            //较大的孩子与父节点比较
            largest = arr[index] > arr[largest] ? index : largest;
            //当前的数已经最大了,不用向下移动。
            if (largest == index) {
                break;
            }
            swap(arr, index, largest);
            //来到大孩子原来位置
            index = largest;
            left = index * 2 + 1;
        }

    }
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

堆的应用

/**
 * 中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
 * [2,3,4]的中位数是 3
 * [2,3] 的中位数是 (2 + 3) / 2 = 2.5
 * 设计一个支持以下两种操作的数据结构:
 * void addNum(int num) - 从数据流中添加一个整数到数据结构中。
 * double findMedian() - 返回目前所有元素的中位数。
 */
public class MedianFinder {
    private PriorityQueue<Integer> minQ;
    private PriorityQueue<Integer> maxQ;
    public MedianFinder() {
        //(默认)小根堆,用于存放大于中位数的数
        this.minQ = new PriorityQueue<>();
        //大根堆,用于存放小于等于中位数的数
        this.maxQ = new PriorityQueue<>((x, y) -> y - x);
    }

    //总是维持 0 <大根堆size-小根堆size <=1; 总量为奇数时,总是peek()大根堆堆顶元素即可
    public void addNum(int num) {
        //还没加过数 或者 当前的数小于大根堆堆顶,加入大根堆,此时的中位数将小于等于原来中位数,因此可能需要移动元素到小根堆。
        if (maxQ.isEmpty() || num <= maxQ.peek()) {
            maxQ.add(num);
            if (minQ.size() + 1 < maxQ.size()) {
                minQ.add(maxQ.poll());
            }
        } else {
            //当前的数大于小根堆堆顶,加入小根堆,此时的中位数将大于等于原来中位数,因此可能需要移动元素到大根堆。
            minQ.add(num);
            if (minQ.size() > maxQ.size()) {
                maxQ.add(minQ.poll());
            }
        }
    }

    public double findMedian() {
        if (maxQ.size() > minQ.size()) {
            return maxQ.peek();
        }
        return (maxQ.peek() + minQ.peek()) / 2.0;  //求平均值,除以2.0得到double类型结果
    }
}

数组习题

  • 两数组中包含唯一(二)不同元素,可以考虑用异或来解
/**
 * 给定一个大小为 n 的数组nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 n/2 的元素。
 * 你可以假设数组是非空的,并且给定的数组总是存在多数元素。
 */
public class leetcode169 {
    //map法
    //Map的value存出现次数 
    public int majorityElement1(int[] nums) {

        Map<Integer, Integer> counts = countNums(nums);

        //相当于是声明了一个键值对的对象,用于存放map中众数的key-value
        Map.Entry<Integer, Integer> majorityEntry = null;
        // map.entrySet  获得的是map中的每一个键值对 (将key-value看成时一个 Map.Entry对象)所构成的set集合
        //可以很方便的遍历同时获得key,value
        for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {
            if (majorityEntry == null || entry.getValue() > majorityEntry.getValue()) {

            }
        }
        for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {
            if (majorityEntry == null || entry.getValue() > majorityEntry.getValue()) {
                majorityEntry = entry;
            }
        }

        return majorityEntry.getKey();
    }

    private Map<Integer, Integer> countNums(int[] nums) {
        Map<Integer, Integer> counts = new HashMap<Integer, Integer>();
        for (int num : nums) {
            if (! counts.containsKey(num)) {
                counts.put(num, 1);
            } else {
                counts.put(num, counts.get(num) + 1);
            }
        }
        return counts;
    }

    //排序,中间位置的数一定时众数
    public int majorityElement2(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length / 2];

    }

    //二分法查找众数,因为众数数量多余一半,因此众数必在左右其中一侧也是众数
    public int majorityElement4(int[] nums) {
        return majorityElementRec(nums, 0, nums.length - 1);
    }


    private int majorityElementRec(int[] nums, int low, int high) {
        if (low == high) return nums[low]; //baseCase
        int mid = low + (high - low) / 2;
        int left = majorityElementRec(nums, low, mid);
        int right = majorityElementRec(nums, mid + 1, high);
        if (left == right) return left;
        //左右区间众数不相同,统计众数出现次数并比较选出答案
        int leftCount = countInRange(nums, low, high, left);
        int rightCount = countInRange(nums, low, high, right);
        return leftCount > rightCount ? left : right;

    }

    private int countInRange(int[] nums, int low, int high, int num) {
        int count = 0;
        for (int i = low; i <= high; i++) {
            if (nums[i] == num) {
                count++;
            }
        }
        return count;
    }

    //投票法
    /**
     * 我们维护一个候选众数 candidate 和它出现的次数 count。初始时 candidate 可以为任意值,count 为 0;
     * 我们遍历数组 nums 中的所有元素,对于每个元素 x,在判断 x 之前,如果 count 的值为 0,我们先将 x 的值赋予 candidate,随后我们判断 x:
     * 如果 x 与 candidate 相等,那么计数器 count 的值增加 1;
     * 如果 x 与 candidate 不等,那么计数器 count 的值减少 1;
     */
    public int majorityElement5(int[] nums) {
        Integer candidate = null;
        int count = 0;
        for (int num : nums) {
            //重新选出一个候选人来
            if (count == 0) {
                candidate = num;
            }
            count += candidate == num ? 1 : - 1;
        }
        return candidate;
    }
}

/**
 * 给你一个整数数组nums 和一个整数k ,判断数组中是否存在两个 不同的索引i和j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。
 * 如果存在,返回 true ;否则,返回 false
 */
public class leetcode219 {
    //map
    public boolean containsNearbyDuplicate1(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            if (map.containsKey(nums[i]) && i - map.get(nums[i]) <= k) {
                return true;
            } else {
                map.put(nums[i], i);
            }
        }
        return false;
    }
	
    //滑动窗口法
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        HashSet<Integer> set = new HashSet<>();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            if (i > k) {
                //每次移除窗口外的元素  [i-k,i] 为窗口大小  如果窗口内含有相同元素,返回true。
                set.remove(nums[i - k - 1]);
            }
            if (! set.add(nums[i])) {  //秀啊  set.add();如果添加成功,返回true 不走if内容。
                return true;
            }
        }
        return false;
    }
}

/**
 * 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
 * 请注意 ,必须在不复制数组的情况下原地对数组进行操作。
 */
public class leetcode283 {
    public void moveZeroes1(int[] nums) {
        if (nums == null) return;
        int j = 0;
        //遇到不是0的数就复制到nums[j]中
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                //j相当与槽位,依次选出非0的填入,最后一个非零元素填入后,j++了,j此时指向了最后一个非零元素的后一位,因此最后要把从j开始的数改为零
                nums[j++] = nums[i];
            }
        }
        //一口气把非0后面的数字设为零
        for (int i = j; i < nums.length; i++) {
            nums[i] = 0;
        }
    }
    /**
     * 使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。
     * 右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。
     *                               类似于快排,把非0的数交换到右边,因为非零数只与0做交换,因此非零数相互间次序不变
     */
    public void moveZeroes2(int[] nums) {
        int n = nums.length, l = 0, r = 0;
        while (r < n) {
            if (nums[r] != 0) {
                swap(nums, l, r);
                l++;
            }
            r++;
        }
    }

    private void swap(int[] nums, int l, int r) {
        int temp = nums[l];
        nums[l] = nums[r];
        nums[r] = temp;
    }
}

二分法查找

 /**
     * 范围查询规律
     * 初始化:
     *   int left = 0;
     *   int right = nums.length - 1;
     * 循环条件
     *   left <= right
     * 右边取值
     *   right = mid - 1
     * 左边取值
     *   left = mid + 1
     * 查询条件
     *   >= target值, 则 nums[mid] >= target时, 都减right = mid - 1
     *   >  target值, 则 nums[mid] >  target时, 都减right = mid - 1
     *   <= target值, 则 nums[mid] <= target时, 都加left = mid + 1
     *   <  target值, 则 nums[mid] <  target时, 都加left = mid + 1
     * 结果
     *   求大于(含等于), 返回left
     *   求小于(含等于), 返回right
     * 核心思想: 要找某个值, 则查找时遇到该值时, 当前指针(例如right指针)要错过它, 让另外一个指针(left指针)跨过他(体现在left <= right中的=号), 则找到了
  • 在排序数组中查找元素的第一个和最后一个位置
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int leftIdx = binarySearch(nums, target, true);  //true表示可以等于
        int rightIdx = binarySearch(nums, target, false) - 1;
        if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] == target && nums[rightIdx] == target) {
            return new int[]{leftIdx, rightIdx};
        } 
        return new int[]{-1, -1};
    }

    public int binarySearch(int[] nums, int target, boolean lower) {
        int left = 0, right = nums.length - 1, ans = nums.length;
        while (left <= right) {  //令left=right=mid,左右相遇之后再走一步跳出循环
            int mid = (left + right) / 2;
            //等于时找最左边等于target元素,大于是找第一个大于target的元素
            if (nums[mid] > target || (lower && nums[mid] >= target)) {
                right = mid - 1;
                ans = mid;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }
}


二叉树

二叉树遍历(递归方法)

重点:递归序!!! 每个节点都会到达三次,根据打印时机不同分为前中后序。

 private void inOrder(TreeNode root) {
        //1
        if (root == null) return;
        inOrder(root.left);
        //2
        System.out.println(root);
        //2
        inOrder(root.right);
        //3
    }


    private void preOrder(TreeNode root) {
        //1
        if (root == null) return;
        System.out.println(root);
        //1
        inOrder(root.left);
        //2
        inOrder(root.right);
        //3
    }

    private void postOrder(TreeNode root) {
        //1
        if (root == null) return;
        inOrder(root.left);
        //2
        inOrder(root.right);
        //3
        System.out.println(root);
        //3
    }

二叉树遍历(非递归方法)

重点:递归序!!! 每个节点都会到达三次,根据打印时机不同分为前中后序。

先序遍历
  1. 将头节点压入栈中
  2. 从栈中弹出一个节点cur
  3. 打印(处理)cur
  4. 先压入cur右孩子,再压入cur左孩子
  5. 周而复始 2~4
后序遍历

相当于pre`(头右左),将打印改成压入到收集栈中,最后统一打印

  1. 将头节点压入栈中
  2. 从栈中弹出一个节点cur
  3. 压入收集栈中
  4. 先压入cur左孩子,再压入cur右孩子
  5. 周而复始 2~4
  6. 最后统一打印
中序遍历
  1. 将头节点压入栈中
  2. 先将左边界节点一路压入栈中
  3. 左边界到头后,弹出左边界最后节点
  4. 如果弹出节点有右孩子的话,周而复始2~4
宽度优先遍历
  1. 准备一个双端队列,并将头节点入队列
  2. 弹出队头节点并打印(处理),并将该节点左右孩子入队
  3. 周而复始 2~ 直至队列为空
层序遍历(特殊宽度优先遍历)
  1. 准备一个双端队列,并将头节点入队列
  2. 在while循环中记录队列元素个数n
  3. 弹出队头节点并打印(处理),并将该节点左右孩子入队
  4. 循环n次3~步骤
  5. 进入下一轮while循环
  6. 周而复始 2~4 直至队列为空
	//先序遍历
   private static void preOrderUnRecur(TreeNode head) {
        if (head != null) {
            Stack<TreeNode> stack = new Stack<>();
            stack.push(head);
            while (stack != null) {
                head = stack.pop();
                System.out.print(head.val + " ");
                if (head.right != null) {
                    stack.push(head.right);
                }
                if (head.left != null) {
                    stack.push(head.left);
                }
            }
        }
    }
	//后序遍历   在先序遍历基础上改造 打印改为压入收集栈中 先压左再压右  保证头右左顺序
    private static void postOrderUnRecur(TreeNode head) {
        if (head != null) {
            Stack<TreeNode> stack = new Stack<>();
            Stack<TreeNode> coll = new Stack<>();
            stack.push(head);
            while (! stack.isEmpty()) {
                head = stack.pop();
                coll.push(head);
                if (head.left != null) {
                    stack.push(head.left);
                }
                if (head.right != null) {
                    stack.push(head.right);
                }
            }
            while (! coll.isEmpty()) {
                System.out.print(coll.pop().val + " ");
            }
        }
    }
	//中序遍历  左头右(左头右(左头右(左头右(...))))   
	//压入栈顺序为先头后左,弹出顺序为先左再头,而找右节点是在弹出打印操作之后发生,因此右节点最后弹出(相当于右树左边界后搞)。
	 private static void inOrderUnRecur(TreeNode head) {
        if (head != null) {
            Stack<TreeNode> stack = new Stack<>();
            while (! stack.isEmpty() || head != null) {
                if (head != null) {
                    stack.push(head);
                    head = head.left;
                } else {
                    head = stack.pop();
                    System.out.println(head.val + " ");
                    head = head.right;
                }
            }
        }
    }

		//宽度优先遍历
	    private static void widthPrioritySearch(TreeNode head) {
        if (head == null) return;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(head);
        while (! queue.isEmpty()) {
            TreeNode cur = queue.poll();
            System.out.println(cur);
            if (cur.left != null) {
                queue.add(cur.left);
            }
            if (cur.right != null) {
                queue.add(cur.right);
            }
        }
    }
	//宽度优先遍历,寻找最大层节点数;
	private static int searchMaxLevelNodes(TreeNode head) {
        if (head == null) return 0;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(head);
        //key为当前节点cur,value为当前节点所在层数curNodeLevel
        Map<TreeNode, Integer> levelMap = new HashMap<>();
        levelMap.put(head, 1);
        int curLevel = 1;
        int curLevelNodes = 0;
        int max = Integer.MIN_VALUE;
        while (! queue.isEmpty()) {
            TreeNode cur = queue.poll();
            int curNodeLevel = levelMap.get(cur);
            if (curNodeLevel == curLevel) {
                curLevelNodes++;
            } else {
                //进入下一层,更新最大层节点数,层数++,当前层节点数重置为1
                max = Math.max(max, curLevelNodes);
                curLevel++;
                curLevelNodes = 1;
            }
            if (cur.left != null) {
                levelMap.put(cur.left, curNodeLevel + 1);
                queue.add(cur.left);
            }
            if (cur.right != null) {
                levelMap.put(cur.right, curNodeLevel + 1);
                queue.add(cur.right);
            }
        }
         return Math.max(max, curLevelNodes);
    }

	//层序遍历、并分层打印节点值
 public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> res = new ArrayList<>();
    Queue<TreeNode> queue = new ArrayDeque<>();
    if (root != null) {
        queue.add(root);
    }
    while (!queue.isEmpty()) {
            //记录当前层节点数
            int n = queue.size();
            List<Integer> level = new ArrayList<>();
            //循环n次,一口气弹出当前层所有节点,并将下一层所有节点全部入队列
        for (int i = 0; i < n; i++) { 
            TreeNode node = queue.poll();
            level.add(node.val);
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
        }
        res.add(level);
    }
    return res;
}

二叉树常见题型

判断搜索二叉树
  • 中序遍历并判断节点值是否满足升序
public class TreeCode {

    public static int preValue = Integer.MIN_VALUE;
	//递归  (推荐)
    public static boolean isBST(TreeNode head) {
        if (head == null) return true;
        boolean isLeftBst = isBST(head.left);
        if (! isLeftBst) {
            return false;
        }
        if (preValue >= head.val) {
            return false;
        } else {
            preValue = head.val;
        }
        return isBST(head.right);
    }
	//递归傻白甜
    public static boolean isBST2(TreeNode head) {
        List<Integer> inOrderList = new ArrayList<>();
        process(head, inOrderList);
        for (Integer nodeVal : inOrderList) {
            if (nodeVal <= preValue) {
                return false;
            } else {
                nodeVal = preValue;
            }
        }
        return true;
    }

    private static void process(TreeNode head, List<Integer> inOrderList) {
        if (head == null) return;
        process(head.left, inOrderList);
        inOrderList.add(head.val);
        process(head.right, inOrderList);
    }
	//非递归
    public static boolean isBST3(TreeNode head) {
        if (head != null) {
            Stack<TreeNode> stack = new Stack<>();
            int preValue = Integer.MIN_VALUE;
            while (stack != null || head != null) {
                if (head != null) {
                    stack.push(head);
                    head = head.left;
                } else {
                    head = stack.pop();
                    if (head.val <= preValue) {
                        return false;
                    } else {
                        preValue = head.val;
                    }
                    head = head.right;
                }
            }
        }
        return true;
    }
}
判断完全二叉树
  • 当前节点有右无左直接返回 false
  • 在遇到非全节点(有一个孩子)后又遇到了非叶子节点 返回false
//判断是否为完全二叉树
    public static boolean isCBT(TreeNode head) {
        if (head == null) {
            return true;
        }
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.add(head);
        boolean leaf = false;
        TreeNode l = null;
        TreeNode r = null;
        while (! queue.isEmpty()) {
            head = queue.poll();
            l = head.left;
            r = head.right;
            if (    //有右无左直接返回   或者  在遇到非全节点后又遇到了非叶子节点
                    l == null && r != null || leaf && (l != null || r != null)
            ) {
                return false;
            }
            if (l != null) {
                queue.add(l);
            }
            if (r != null) {
                queue.add(r);
            }
            //遇到非全节点标记为true
            if (l == null || r == null) {
                leaf = true;
            }
        }
        return true;
    }
判断平衡二叉树
  • 任一节点的两子树高度差不超过一·
  • 递归套路: 假设可以从其左树和右树上要信息 定结构体(即需要子树返回的信息)
//判断是否为平衡二叉树
public static boolean isBalanced(TreeNode head) {  //主函数
    return process2(head).isBalanced;
}

public static ReturnType process2(TreeNode x) {
    if (x == null) return new ReturnType(true, 0);  //base
    //递归地向左右树要信息
    ReturnType leftData = process2(x.left);
    ReturnType rightData = process2(x.right);
    //根据左右树信息加工出自己的信息,作为返回值。
    boolean isBalanced = leftData.isBalanced && rightData.isBalanced
            && (Math.abs(leftData.height - rightData.height) < 2);
    int height = Math.max(leftData.height, rightData.height) + 1;
    return new ReturnType(isBalanced, height);
}
//返回值结构体,分析需要向左右树要哪些信息
public static class ReturnType {
    public boolean isBalanced;
    public int height;

    public ReturnType(boolean isBalanced, int height) {
        this.isBalanced = isBalanced;
        this.height = height;
    }
}
//判断搜索二叉树  递归方法
public static SearchReturnType process3(TreeNode x) {
        if (x == null) return null;
        SearchReturnType leftData = process3(x.left);
        SearchReturnType rightData = process3(x.right);
        int min = x.val;
        int max = x.val;
        if (leftData != null) {
            min = Math.min(min, leftData.min);
            max = Math.max(max, leftData.max);
        }
        if (rightData != null) {
            min = Math.min(min, rightData.min);
            max = Math.max(max, rightData.max);
        }
        boolean isBST = true;
        if (leftData != null && (! leftData.isBST || leftData.max >= x.val)) {
            isBST = false;
        }
        if (rightData != null && (! rightData.isBST || rightData.min <= x.val)) {
            isBST = false;
        }
        if (leftData != null && rightData != null) {
        }
        return new SearchReturnType(isBST, max, min);
    }

    public static class SearchReturnType {
        public boolean isBST;
        public int max;
        public int min;

        public SearchReturnType(boolean isBST, int max, int min) {
            this.isBST = isBST;
            this.max = max;
            this.min = min;
        }
    }
//判断满二叉树  递归方法   简单
public static boolean isFBT(TreeNode head) {
        if (head == null) return true;
        ReturnInfo returnInfo = process4(head);
        return ((1 << returnInfo.height) - 1) == returnInfo.nodes;
    }

    private static ReturnInfo process4(TreeNode x) {
        if (x == null) return new ReturnInfo(0, 0);
        ReturnInfo leftData = process4(x.left);
        ReturnInfo rightData = process4(x.right);
        int nodes = leftData.nodes + rightData.nodes + 1;
        int height = Math.max(leftData.height, rightData.height) + 1;
        return new ReturnInfo(nodes, height);
    }

    public static class ReturnInfo {
        public int nodes;
        public int height;

        public ReturnInfo(int nodes, int height) {
            this.nodes = nodes;
            this.height = height;
        }   
 }
二叉树习题
  1. 找最近公共祖先
//递归方法找公共祖先
    public static TreeNode lowestCommonAncestor(TreeNode head, TreeNode o1, TreeNode o2) {
    if (head == null || head == o1 || head == o2) {
        return head;
    }
    TreeNode left = lowestCommonAncestor(head.left, o1, o2);
    TreeNode right = lowestCommonAncestor(head.right, o1, o2);
    if (left != null && right != null) {
        return head;
    }
    return left != null ? left : right;
}
    
    //非递归方法,寻找两节点最低公共祖先
public static TreeNode lca(TreeNode head, TreeNode o1, TreeNode o2) {
    //用来存放当前节点的父节点
    Map<TreeNode, TreeNode> fatherMap = new HashMap<>();
    fatherMap.put(head, head);
    process(head, fatherMap);
    TreeNode cur1 = o1;
    HashSet<TreeNode> set = new HashSet<>();
    set.add(head);
    //当前节点不是头节点
    while (cur1 != fatherMap.get(cur1)) {
        //set中保存o1父路径
        set.add(cur1);
        cur1 = fatherMap.get(cur1);
    }
    TreeNode cur2 = o2;
    while (! set.contains(cur2)) {
        cur2 = fatherMap.get(cur2);
    }
    return cur2;
}
private static void process(TreeNode head, Map<TreeNode, TreeNode> fatherMap) {
    if (head == null) return;
    fatherMap.put(head.left, head);
    fatherMap.put(head.right, head);
    process(head.left, fatherMap);
    process(head.right, fatherMap);
}
  1. 微软面试题,打印折纸折痕
//从上至下打印折纸折痕凹凸情况
public static void printAllFolders(int N){
    printProcess(1,N,true);
}
//满二叉树
//i为当前节点层数,N为折纸次数即树的深度,down为true 凹 
private static void printProcess(int i, int N, boolean down) {
    //层数大于树深,直接返回
    if (i>N) return;
    //中序遍历
    printProcess(i+1, N, true);
    System.out.print(down?"凹":"凸");
    printProcess(i+1, N, false);
}
  1. 给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树
public TreeNode sortedArrayToBST(int[] nums) {
    return dfs(nums, 0, nums.length - 1);
}
//因为要满足平衡树,因此要选中位值为头节点
//为满足搜索树,左子树的节点从mid左边找,右子树的节点从mid右边找
private TreeNode dfs(int[] nums, int l, int r) {
    if (l > r) return null;  //base  左边界大于右边界返回空
    int mid = l + ((r - l) >> 1);
    TreeNode head = new TreeNode(nums[mid]);
    //递归构建左右子树
    head.left = dfs(nums, l, mid - 1);
    head.right = dfs(nums, mid + 1, r);
    return head;
}
  1. 给定一个二叉树,找出其最小深度。(递归)
    • 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
public int minDepth(TreeNode root) {
    if (root == null) return 0;
    //当前节点为叶子节点
    if (root.left == null && root.right == null) return 1;
    int l = minDepth(root.left);
    int r = minDepth(root.right);
    //当前节点只有一个孩子  l、r中必然有一个为0
    if (root.left == null || root.right == null) {
        return l + r + 1;
    }
    //两个孩子都在
    return Math.min(l, r) + 1;
}
  1. 给你一个二叉树的根节点 root ,按任意顺序 ,返回所有从根节点到叶子节点的路径。
  • 递归方法!!!
public class leetcode257 {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> paths = new ArrayList<>();
        constructPaths(root, "", paths);
        return paths;
    }

    private void constructPaths(TreeNode root, String path, List<String> paths) {
        if (root != null) {
            StringBuilder pathSB = new StringBuilder(path);
            pathSB.append(Integer.toString(root.val));
            //当前节点为叶子节点
            if (root.left == null && root.right == null) {
                paths.add(pathSB.toString());
            } else {
                pathSB.append("->");
                constructPaths(root.left, pathSB.toString(), paths);
                constructPaths(root.right, pathSB.toString(), paths);
            }
        }
    }
}
  1. 给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
public List<String> binaryTreePaths(TreeNode root) {
    List<String> paths = new ArrayList<>();
    constructPath(root, "", paths);
    return paths;
}

private void constructPath(TreeNode root, String path, List<String> paths) {
    if (root != null) {
        StringBuilder pathSB = new StringBuilder(path);
        pathSB.append(Integer.toString(root.val));
        //叶子节点
        if (root.left == null && root.right == null) {
            paths.add(pathSB.toString());
        } else {
            pathSB.append("->");
            constructPath(root.left, pathSB.toString(), paths);
            constructPath(root.right, pathSB.toString(), paths);
        }
    }
}
  1. 给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
public class leetcode530 {

    int ans, pre;

    public int getMinimumDifference(TreeNode root) {
        ans = Integer.MAX_VALUE;
        pre = - 1;
        dfs(root);
        return ans;
    }
	//中序遍历
    private void dfs(TreeNode x) {
        if (x == null) return;
        dfs(x.left);
        update(x.val);  //处理逻辑
        dfs(x.right);
    }
    private void update(int x) {
        //第一个节点
        if (pre == - 1) {
            pre = x;
        } else {
            ans = Math.min(x - pre, ans);
            pre = x;
        }
    }
}
  1. 给你一个二叉树的根节点 root ,计算并返回 整个树 的坡度 。
/**
 * 给你一个二叉树的根节点 root ,计算并返回 整个树 的坡度 。
 * 一个树的 节点的坡度 定义即为,该节点左子树的节点之和和右子树节点之和的 差的绝对值 。如果没有左子树的话,左子树的节点之和为 0 ;没有右子树的话也是一样。空结点的坡度是 0 。
 * 整个树 的坡度就是其所有节点的坡度之和。
 */
public class leetcode563 {
      public int findTilt(TreeNode root) {
        if (root == null) return 0;
        //返回 当前坡度+左子树坡度和+右子树坡度和
        return Math.abs(sum(root.left) - sum(root.right)) + findTilt(root.left) + findTilt(root.right);
    }

    public int sum(TreeNode x) {
        if (x == null) return 0;
        return sum(x.left) + sum(x.right) + x.val;
    }
    
	//坡度差之和
    int res;
    public int findTilt2(TreeNode root) {
        dfs(root);
        return res;
    }
    //dfs就是上面的sum方法,多了一步把每个节点的坡度差累加到res中。
    private int dfs(TreeNode x) {
        if (x == null) return 0;
        int sumLeft = dfs(x.left);
        int sumRight = dfs(x.right);
        //计算每个节点坡度,并累加
        res += Math.abs(sumLeft - sumRight);
        return sumLeft + sumRight + x.val;
    }
}
  1. 给定一棵二叉树,你需要计算它的直径长度。
/**
 * 给定一棵二叉树,你需要计算它的直径长度。  求长度,因此求出的节点个数最后要减 1
 * 一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
 */
public class leetcode543 {
    //两次递归
    public int diameterOfBinaryTree(TreeNode root) {
        return f(root) - 1;
    }
    public int f(TreeNode root) {
        if (root == null) return 0;
        //过头节点
        int max1 = dfs(root.left) + dfs(root.right) + 1;
        //不过头节点
        int max2 = Math.max(f(root.left), f(root.right));
        return Math.max(max1, max2);
    }
    //查看以x为头的树的高度
    private int dfs(TreeNode x) {
        if (x == null) return 0;
        return Math.max(dfs(x.left), dfs(x.right)) + 1;
    }
    //单次递归方法  (声明一个全局变量,用于记录最长路径节点数)
    int res;
    public int diameterOfBinaryTree2(TreeNode root) {
        res = 1;
        depth(root);
        return res - 1;
    }
    private int depth(TreeNode x) {
        if (x == null) return 0;
        int L = depth(x.left);
        int R = depth(x.right);
        res = Math.max(res, L + R + 1);   //过当前节点时该子树最大深度  遍历每个节点
        return Math.max(res, Math.max(L, R) + 1);
    }
}
  1. 给你两棵二叉树 root 和 subRoot 。
  • 没有优化
 给你两棵二叉树 root 和 subRoot 。
 检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 falsepublic boolean isSubtree(TreeNode r, TreeNode s) {
    return dfs(r, s);
}
private boolean dfs(TreeNode r, TreeNode s) {
    if (r == null) return false;
    return dfs(r.left, s) || dfs(r.right, s) || check(r, s);
}
//判断两课树是否相同
private boolean check(TreeNode r, TreeNode s) {
    if (r == null && s == null) return true;
    //只有一个为空,或者 两者值不相等
    if (r == null || s == null || r.val != s.val) return false;
    return check(r.left, s.left) && check(r.right, s.right);
}

树形DP套路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XCNL1uJr-1665234185479)(C:\Users\DFL\AppData\Roaming\Typora\typora-user-images\image-20221005201817946.png)]

位运算

/**
 * 输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数
 * 算术右移 >> :舍弃最低位,高位用符号位填补;						
 * 逻辑右移 >>> :舍弃最低位,高位用 0 填补。					符号左边表示要移动的二进制数,符号右边表示要移动多少位。
 */
public int hammingWeight1(int n) {
    int res = 0;
    while (n != 0) {
        res += n & 1;
        //逻辑右移
        n >>>= 1;
    }
    return res;
}

public int hammingWeight2(int n) {
        int res = 0;
        while (n != 0) {
            ++ res;
            //消除最右侧的1
            n &= (n - 1);
        }
        return res;
    }
/**
 * 给定两个有符号32位整数a和b,不能使用算术运算符,分别实现a和b的加、减、乘、除运算
 */
 public static int add(int a, int b) {
        int sum = 0;
        while (b != 0) {    //当进位信息为0时,表示不用加了,无进位相加结果即为最终求和结果
            sum = a ^ b;  //sum记录无进位相加结果
            b = (a & b) << 1; //b记录进位信息
            a = sum;  //得到进位信息和无进位相加结果后,需要相加得到最终结果,因此继续上面操作
        }
        return sum;
    }

    public static int minus(int a, int b) {
        return add(a, negNum(b));

    }

    private static int negNum(int n) {
        return add(~ n, 1);  //一个数的相反数为,它自己取反后加1
    }

    public static int multi(int a, int b) {
        int res = 0;
        while (b != 0) {
            if ((b & 1) != 0) {  //b末尾为1就累加a
                res = add(res, a);
            }
            a <<= 1;
            b >>>= 1;  //左移右移保证了结果不变,每次把b中的1移到末尾,然后乘上a右移变大后的结果
        }
        return res;
    }

private static boolean isNeg(int n) {
        return n < 0;
    }
    													//  代码一直跑,有问题啊
    public static int div(int a, int b) {
        int x = isNeg(a) ? negNum(a) : a;
        int y = isNeg(b) ? negNum(b) : b;
        int res = 0;
        for (int i = 31; i > - 1; minus(i, 1)) {
            if ((x >> i) >= y) {  //y右移不安全,可能出现负数
                res |= (1 << i); //x右移前从左往右第一个1,肯定是原来另一个因子那个位置(1<<i)上有1,才让它能移动这么多位
                x = minus(x, y << i);  //让x等于减后的值继续循环
            }
        }
        //符号不同
        return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
    }

    public static int divide(int a, int b) {
        if (b == 0) {
            throw new RuntimeException("divisor is 0");
        }
        if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
            return 1;
        } else if (b == Integer.MAX_VALUE) {
            return 0;
        } else if (a == Integer.MIN_VALUE) {
            int res = div(add(a, 1), b);
            return add(res, div(minus(res, b), b));
        }
        return div(a, b);
    }
//交换
public static void sway(int[] arr,int i,int j){
	if(i!=j){
		//不能两个值指向同一地址
	    arr[i]=arr[i]^arr[j];
	    arr[j]=arr[i]^arr[j];//就是arr[i]^arr[j]^arr[j]就表示a
	    arr[i]=arr[i]^arr[j];//表示arr[i]^arr[j]^arr[i]^arr[j]^arr[j]就是b
    }
}
// mostRightOne值在二进制位上的位次就是pos得最右第一个1的位置
int mostRightOne = pos & (~pos + 1); 

//消除二进制末尾的 1
pos & (pos-1)

//整数二进制奇数位偶数位交换
private static int process(int pos) {
		int pre = 0xAAAAAAAA; // 1010 1010 1010 1010 1010 1010 1010 1010 
		int post = 0x55555555; // 0101 0101 0101 0101 0101 0101 0101 0101 
		pre &= pos;
		post &= pos;
		pre >>= 1;
		post <<= 1;
		return pos + post;
	}

//给定一个整数数组,只有一个数出现了一次,其他数字均出现了三次,输出这一个只出现一次的数。
public static int twoSingleNum(int[] arr) {
		int[] bit = new int[32];// 每一位求和
		for (int a : arr) {
			int b = 1;
			for (int i = 31; i >= 0; --i) {
				if ((a & b) != 0) {// 为1就累加
					++bit[i];
				}
				b <<= 1;// 换位
			}
		}
		int res = 0;
		for (int i = 0; i < 32; ++i) {
			res = res << 1;
			res += (bit[i] % 3);// 取余数
		}
		return res;
	}

**
 * 给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。
 * 输入:a = "11", b = "1"
 * 输出:"100"
 */
public class leetcode67 {
    public String addBinary(String a, String b) {
        //求出累加次数
        int maxLength = Math.max(a.length(), b.length());
        StringBuilder sb = new StringBuilder();
        int carry = 0;  //carry % 2 为本位数字,carry / 2 为下一位的进位
        for (int i = 0; i < maxLength; i++) {
                        // i小于各自长度                从最后一位开始计算,
            carry += i < a.length() ? (a.charAt(a.length() - 1 - i) - '0') : 0;
            carry += i < b.length() ? (b.charAt(b.length() - 1 - i) - '0') : 0;
            //这里注意转化为字符,carry%2加上个'0';
            sb.append((char) (carry % 2 + '0'));
            //下一位的进位信息
  		   carry /= 2;
        }
        if (carry > 0) {
            sb.append('1');
        }
        sb.reverse();
        return sb.toString();
    }
    
    	// public String toString(int radix)  radix默认十进制     //在线OJ(Online Judge)貌似不支持
    public static String addBinary2(String a, String b) {
        //将a、b由二进制转为十进制,然后调用toString(int radix),将十进制数转为指定进制字符串
        return new BigInteger(a, 2).add(new BigInteger(b, 2)).toString(2);
    }
}

/**
 * 给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。
 * 输入:n = 2
 * 输出:[0,1,1]
 * 解释:
 * 0 --> 0
 * 1 --> 1
 * 2 --> 10
 */
public class leetcode338 {
    //1、朴素
    public int[] countBits(int n) {
        int[] bits = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            bits[i] = countOnes(i);
        }
        return bits;
    }

    private int countOnes(int x) {
        int ones = 0;
        while (x > 0) {
            ones++;
            x &= (x - 1);  //消除最右侧的1
        }
        return ones;
    }

    //2、DP  右移后补上右移操作丢掉的最末尾的数
    public int[] countBits2(int n) {
        int[] bits = new int[n + 1];
//        bits[0] = 0;     也可以不设置,因为int数组每个元素默认初始化都是0
        for (int i = 1; i <= n; i++) {      //对于正整数 x,将其二进制表示右移一位,等价于将其二进制表示的最低位去掉
            bits[i] = bits[i >> 1] + (i & 1);   //先取出最后一位数,奇数为1,偶数为0,当前数1的个数等于该数右移后1的个数+移动前最末尾的数
        }
        return bits;
    }

    //3、DP  消除最右侧1后加 1
    public int[] countBits3(int n) {
        int[] bits = new int[n + 1];
//        bits[0] = 0;
        for (int i = 1; i <= n; i++) {
            bits[i] = bits[i & i - 1] + 1;  //i & i - 1 消除最末尾的1,然后在末尾处在加上1,保持与原来的数1的个数相同
        }
        return bits;
    }

    public static void main(String[] args) {
        int[] bits = new int[4];
        for (int i = 0; i < bits.length; i++) {
            System.out.println(bits[i]);
        }
    }
}
/**
 * 给定一个整数,写一个函数来判断它是否是 4 的幂次方。如果是,返回 true ;否则,返回 false 。
 */
 public static boolean isPowerOfFour1(int n) {
        //n小于等于0 false ,n不是2的整数幂 false  二进制表示中  1在偶数位 false
        return n > 0 && (n & (n - 1)) == 0 && (n & (0xAAAAAAAA)) == 0;
    }

 public static boolean isPowerOfFour2(int n) {
        //n小于等于0 false ,n不是2的整数幂 false  n^x % (n-1) 恒等于 1,相当与每个都n都%(n-1)然后相乘;
        return n > 0 && (n & (n - 1)) == 0 && n % 3 == 1;
    }

/**
 * 不使用比较符号,选出两数较大值,运用了两互斥条件相加来完成if else操作
 */
public static int getMax(int a, int b) {
        int c = a - b;
        int sa = sign(a);   //正数为1,负数为0  0、1起到了相加互斥的效果
        int sb = sign(b);
        int sc = sign(c);
        int difSab = sa ^ sb;  //相同为0,不同为1
        int sameSab = flip(difSab); //相同为1,不同为0
        //返回a的条件  ab符号不同 a>0返回a,ab符号相同 a-b>=0 返回a
        int returnA = difSab * sa + sameSab * sc;  //互斥,用加号    a>b返回 a  a大于0,sa才会为1,总的结果为1  c大于0,sc才会为1,总的结果为1 
        int returnB = flip(returnA);
        return a * returnA + b * returnB;
    }

    private static int flip(int x) {
        return x ^ 1;        //符号取反
    }

    private static int sign(int x) {
        return flip((x >> 31) & 1);  //正数为1,负数为0
    }

奇怪题

/**
 * 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
 * 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
 * 如果这个过程 结果为 1,那么这个数就是快乐数。
 */
  //本题关键点为 totalSum  不会越来越大  要么变为1 要么死循环
public class leetcode202 {

    public boolean isHappy(int n) {
        HashSet<Integer> seen = new HashSet<>();
        //没有变成 1 或者 没有进入死循环
        while (n != 1 && ! seen.contains(n)) {
            seen.add(n);
            n = getNext(n);
        }
        return n == 1;
    }

    private int getNext(int n) {
        int totalSum = 0;
        while (n != 0) {
            int d = n % 10;
            n /= 10;
            totalSum += d * d;
        }
        return totalSum;
    }

    //隐式链表   判断有环无环
    public boolean isHappy2(int n) {
        int slow = n;
        int fast = getNext(n);
        while (fast != 1 && fast != slow) {
            slow = getNext(n);
            fast = getNext(getNext(fast));
        }
        return fast == 1;
    }
}

/**
 * 丑数 就是只包含质因数 2、3 和 5 的正整数。
 * 给你一个整数 n ,请你判断 n 是否为 丑数 。如果是,返回 true ;否则,返回 false 。
 *  丑数可以表示为2^a * 3^b * 5^c
 */
 public boolean isUgly(int n) {
        if (n <= 0) return false;
        int[] factors = {2, 3, 5};
        for (int factor : factors) {
            //先一直除一个因数,如果一直能除尽,则n = 1,除不尽换另一个因数尝试,
            while (n % factor == 0) {
                n /= factor;
            }
        }
        return n == 1;
    }


链表

dummyNode

  • 在头节点钱可以设置一个空节点dummyNode,这样就可以避免一些问题中的头节点特殊处理,比如在K个一组反转链表问题中:

但是对于第一个子链表,它的头节点 head 前面是没有节点 pre 的。太麻烦了!难道只能特判了吗?答案是否定的。没有条件,我们就创造条件;没有节点,我们就创建一个节点。我们新建一个节点,把它接到链表的头部,让它作为 pre 的初始值,这样 head 前面就有了一个节点,我们就可以避开链表头部的边界条件。这么做还有一个好处,下面我们会看到。

/**
 * 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
 */
public class leetcode203 {
    //递归,先对头节点以外的元素进行删除操作
    public ListNode removeElements1(ListNode head, int val) {
        if (head == null) return null;
        //head 抓住其他节点删除操作后返回的新头
        head.next = removeElements1(head.next, val);
        //如果head值等于val,则移除head
        return head.val == val ? head.next : head;
    }

    public ListNode removeElements2(ListNode head, int val) {
        ListNode dummyNode = new ListNode(0);
        dummyNode.next = head;
        ListNode cur = dummyNode;
        while (cur.next != null) {
            if (cur.next.val == val) {
                //移除链表当前节点下一个元素
                cur.next = cur.next.next;
            } else {
                cur =cur.next;
            }
        }
        return dummyNode.next;
    }
}

/**
 * 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
 */
public class leetcode206 {
    public ListNode reverseList1(ListNode head) {
        //记录前驱节点
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            //记录后继节点
            ListNode next = cur.next;
            //此时可以放心修改cur.next指向
            cur.next = pre;
            //pre、cur 都向后移动
            pre = cur;
            cur = next;
        }
        return pre;
    }

    /**
     * A->B->C->D->E
     * after reversing
     * A->B->NULL  NULL<-B<-C<-D<-E  A、B之间构成双向链表
     */
    public ListNode reverseList2(ListNode head) {
        if (head == null || head.next == null) return head; //baseCase
        //以B为头节点的链表反转后,A仍然持有B的引用 A.next = B
        ListNode newHead = reverseList2(head.next);
        // 令 A<-B
        head.next.next = head;
        //令 NULL<-A
        head.next = null;
        return newHead;
    }
}

/**
 * 给你链表的头节点 head ,每k个节点一组进行翻转,请你返回修改后的链表。
 * k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是k的整数倍,那么请将最后剩余的节点保持原有顺序。
 */
public class leetcode25 {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode hair = new ListNode(0);
        hair.next = head;
        ListNode pre = hair;  //pre指向子链表反转后的末尾位置,用于拼接后面的串
        //不足k个直接返回原先的头节点
        while (head != null) {
            ListNode tail = pre;  //每次从pre位置跑k步指向第k个节点位置
            for (int i = 0; i < k; i++) {
                tail = tail.next;
                if (tail == null) {
                    return hair.next;
                }
            }
            ListNode next = tail.next;  //pre tail两个变量记录反转前子串头节点前一个位置、结尾位置,用于将反转后的子串拼接回原链表
            ListNode[] reverse = myReverse(head, tail);
            //反转后新头尾
            head = reverse[0];
            tail = reverse[1];
            //重新将子串拼接回链表
            pre.next = head;
            tail.next = next;
            pre = tail;  //下一组
            head = tail.next;
        }
        return hair.next;
    }

    private ListNode[] myReverse(ListNode head, ListNode tail) {
        ListNode pre = tail.next;  //反转后新的尾节点指向原来尾节点下一个
        ListNode cur = head;
        while (pre != tail) {  //当前节点cur来到了尾部
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }//                   新头  新尾
        return new ListNode[]{tail, head};
    }
}

字符串

  • 经常地,可以创建一个长度26的字母频次统计数组,index为字母 -‘ a’,值为字母出现频次,也可以创建128的char[ ]数组。
/**
 * 给定两个字符串 s 和 t ,判断它们是否是同构的。 潜台词:字符是否一一映射。
 * example: egg add
 *  					一个map不能保证一一映射
 */  
		//  两个map
  public boolean isIsomorphic(String s, String t) {
        Map<Character, Character> s2t = new HashMap<Character, Character>();
        Map<Character, Character> t2s = new HashMap<Character, Character>();
        int len = s.length();
        for (int i = 0; i < len; ++i) {
            char x = s.charAt(i), y = t.charAt(i);
            if ((s2t.containsKey(x) && s2t.get(x) != y) || (t2s.containsKey(y) && t2s.get(y) != x)) {
                return false;
            }
            s2t.put(x, y);
            t2s.put(y, x);
        }
        return true;
    }
	//通过indexOf();  判断元素第一次出现位置是否相同
    public boolean isIsomorphic2(String s, String t) {
        int n = s.length();
        for (int i = 0; i < n; i++) {
            //index Of();没有找到返回=1
            if (s.indexOf(s.charAt(i)) != t.indexOf(t.charAt(i))) {   //关键
                return false;
            }
        }
        return true;
    }

/**								与上题解法2异曲同工          都是记录元素第一次出现的位置,在这个位置第一次确定了两个元素的映射关系
*
 * 给定一种规律 pattern和一个字符串s,判断 s是否遵循相同的规律。  abba "cat dog dog cat"
 * 这里的遵循指完全匹配,例如,pattern里的每个字母和字符串s中的每个非空单词之间存在着双向连接的对应规律。
 * 		public V put(K key, V value) {       如果key不存在,插入成功,返回null;如果key存在,返回之前对应的value。
 * 			return putVal(hash(key), key, value, false, true);
 * }
 */
public static boolean wordPattern(String pattern, String s) {
    String[] words = s.split(" ");
    if (words.length != pattern.length()) return false;
    //key 为元素,value为第一次出现是的数组下标
    HashMap<Object, Integer> map = new HashMap<>();
    for (Integer i = 0; i < pattern.length(); i++) {
            //如果key不存在,插入成功,返回null;如果key存在,返回之前对应的value。
            //value 为元素第一次出现时的数组下标
            if (map.put(pattern.charAt(i), i) != map.put(words[i], i)) {
                return false;
            }
    	}
    return true;
} 

/**
 * 给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
 * 如果可以,返回 true ;否则返回 false 。   其实就是判断 ransomNote是不是magazine的顺序打乱了的子串
 * magazine 中的每个字符只能在 ransomNote 中使用一次。
 */
 public boolean canConstruct(String ransomNote, String magazine) {
        if (ransomNote.length() > magazine.length()) return false;
        //创建长度26的数组,用来统计magazine中每个字母出现的次数,下标为 字母-'a',值为出现次数
        int[] cnt = new int[26];
     //统计magizine中字母出现次数
        for (char c : magazine.toCharArray()) {
            cnt[c - 'a']++;
        }
     //如果ransomNote 中某一字母出现次数比magazine  直接false
        for (char c : ransomNote.toCharArray()) {
            cnt[c - 'a']--;
            if (cnt[c - 'a'] < 0) {
                return false;
            }
        }
        return true;
    }

/**
 * 给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。
 */
public int firstUniqChar(String s) {
        char[] dict = new char[26];
   	     //第一遍统计字母出现次数    loveleetcode
    						    221424411214
        for (int i = 0; i < s.length(); i++) {
            int index = s.charAt(i) - 'a';
            dict[index]++;
        }
        //第二遍 查字典 找出只出现一次的字母  还是按原来字符串顺序一个个查找,因此能保证找到第一个不重复的字符
        for (int i = 0; i < s.length(); i++) {
            int index = s.charAt(i) - 'a';
            if (dict[index] == 1) {
                return i;
            }
        }
        return - 1;
    }

贪心策略

  • 最常用的就是堆、排序
/*
切割金条花费最少问题
*/
public static int lessMoney(int[] arr) {
    //小根堆
    PriorityQueue<Integer> pQ = new PriorityQueue<>();
    for (int i : arr) {
        pQ.add(i);
    }
    int cur = 0;
    int sum = 0;
    while (pQ.size() > 1) {
        //每次弹出两个做结合,将值放入小根堆中直至小根堆数量为1
        cur = pQ.poll()+ pQ.poll();
        sum +=cur;
        pQ.add(sum);
    }
    return sum;
}
/**
 * 从当前给定项目中挑选出一些使手中资金最大化。
 */
public class IPO {
    public static class Node {
        public int profit;
        public int cost;
        public Node(int profit, int cost) {
            this.profit = profit;
            this.cost = cost;
        }

    }

    public static class MinCostComparator implements Comparator<Node> {
        @Override
        public int compare(Node o1, Node o2) {
            return o1.cost - o2.cost;
        }
    }

    public static class MaxProfitComparator implements Comparator<Node> {
        @Override
        public int compare(Node o1, Node o2) {
            return o2.profit - o1.profit;
        }
    }

    //k 进行k轮  w手中的资金 profit、capital每个项目的利润以及收益
    public static int findMaximizedCapital(int k, int w, int[] profit, int[] capital) {
        PriorityQueue<Node> minCostQ = new PriorityQueue<>(new MinCostComparator());
        PriorityQueue<Node> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
        //将所有项目扔进由花费组织的小根堆
        for (int i = 0; i < profit.length; i++) {
            minCostQ.add(new Node(profit[i], capital[i]));
            
        for (int i = 0; i < k; i++) {
                //还有项目   并且  手中资金能够解锁项目
                while (! minCostQ.isEmpty() && minCostQ.peek().cost <= w) {
                    maxProfitQ.add(minCostQ.poll());
                }
                //没项目能选了,提前返回
                if (maxProfitQ.isEmpty()) {
                    return w;
                }
                w += maxProfitQ.poll().profit;
            }
        }
        return w;
    }
}
//也行,不过跑起来没上面性能好 使用了lambda表达式  可以简化代码
public static int findMaximizedCapital2(int k, int w, int[] profit, int[] capital) {
        int n = profit.length;
        int cur = 0;
        int[][] arr = new int[n][2];
        for (int i = 0; i < n; i++) {
            //第一个表示所需资金,第二个表示可获得收益
            arr[i][0] = capital[i];
            arr[i][1] = profit[i];
        }
        //lambda表达式传入比较器    根据所需资金排个序
        Arrays.sort(arr, (a, b) -> a[0] - b[0]);
        //由净利润组成的大根堆
        PriorityQueue<Integer> pq = new PriorityQueue<>((x, y) -> y - x);
        for (int i = 0; i < k; i++) {
            while (cur < n && arr[cur][0] <= w) {
                pq.add(arr[i][1]);
                cur++;
            }
            if (! pq.isEmpty()) {
                w += pq.poll();
            } else {
                break;
            }
        }
        return w;
    }

暴力递归

  • 暴力递归就是尝试
  1. 把问题转化为规模缩小了的同类问题的子问题
  2. 有明确的不需要继续进行递归的条件(base case)
  3. 有当得到了子问题的结果之后的决策过程
  4. 不记录每一个子问题的解
  • 返回值往往是0 ~ i - 1位置选择确定的情况下,i 位置如何选择返回其结果
  • 在尝试的过程中找到可变参数形式最简单可变参数个数最少的试法
/**
 * N皇后问题,没法做总体时间复杂度优化,可以做常数优化。
 * num1() res = 365596
 * cost time4244ms
 * num2() res = 365596
 * cost time180ms
 */
public class NQueens {
    public static int num1(int n) {
        int[] record = new int[n];  //每一个值表示,在第i行皇后放在那一列
        return process1(0, record, n);
    }
    //record[0,1,2...,i-1]表示0-i-1行,皇后放在了那一列
    //i表示当前在第i行
    //n表示总共多少行多少列
    //返回值表示 摆完所有的皇后,合理摆法有多少种
    private static int process1(int i, int[] record, int n) {
        if (i == n) return 1;  //来到了终止行 ([0,n-1])
        int res = 0;

        for (int j = 0; j < n; j++) { //当前在第i行,尝试所有的列
            //如果当前列可以摆,进行深度优先遍历,寻找下一行可选的摆法
            if (isValid(record, i, j)) {
                record[i] = j;
                res += process1(i + 1, record, n);
            }
        }
        return res;
    }

    private static boolean isValid(int[] record, int i, int j) {
        //遍历之前的行  看之前所摆的皇后与第i行皇后是否同列或共斜线
        for (int k = 0; k < i; k++) {
            if (record[k] == j || Math.abs(record[k] - j) == Math.abs(i - k)) {
                return false;
            }
        }
        return true;
    }
	//方法二:使用位运算
    public static int num2(int n) {
        if (n < 1 || n > 32) {
            return 0;
        }
        //限制可选范围  后n位是1,前面都是0  该步骤旨在将可供选择的列限制在指定范围内,后面与其他限制取 & 时 前面的0不造成影响
        int limit = n == 32 ? - 1 : (1 << n) - 1;  -1的二进制表示为321    (1 << n)注意时1左移,1放前边
        return process2(limit, 0, 0, 0);
    }

    //1地位置可以放皇后,0的位置不行。
    private static int process2(
            int limit,   //总的可选范围
            int colLim, //列限制
            int leftDiaLim,  //左对角线限制
            int rightDialim) {  //右对角线限制
        //base case
        if (colLim == limit) return 1;  //列限制数量与可供选择列数量相同,该结果为一个合法的结果。

        int mostRightOne = 0;
        //当前可供选择的列   在总的可选范围上 排除其他限制
        int pos = limit & (~ (colLim | leftDiaLim | rightDialim));

        int res = 0;
        //还有位置可选时
        while (pos != 0) {
            mostRightOne = pos & (~ pos + 1);  //从最右侧1开始遍历
            pos -= mostRightOne;  //消除最右侧1,pos移到下一个1继续尝试
            res += process2(limit,
                    colLim | mostRightOne,
                    (leftDiaLim | mostRightOne) << 1,
                    (rightDialim | mostRightOne) >>> 1);  //无符号右移 32皇后最高位为1 右移后最高位拿0来补 
        }
        return res;
    }

    public static void main(String[] args) {
        int n = 14;
        long start = System.currentTimeMillis();
        System.out.println(num1(n));
        long end = System.currentTimeMillis();
        System.out.println("cost time" + (end - start)+"ms");

        start = System.currentTimeMillis();
        System.out.println(num2(n));
        end = System.currentTimeMillis();
        System.out.println("cost time" + (end - start)+"ms");
    }
}
/**
 * Tower of Hanoi
                                                Move 1 from 左 to 中
                                                Move 2 from 左 to 右
                                                Move 1 from 中 to 右
                                                Move 3 from 左 to 中
                                                Move 1 from 右 to 左
                                                Move 2 from 右 to 中
                                                Move 1 from 左 to 中
 */
public static void hanoi(int n) {
        if (n > 0) {
            func(n, "左", "中", "右");   //从左移动到中
        }
    }
    //i为盘子编号,从上至下1~n      每次移动保证自己的底不违规
    private static void func(int i, String from, String to, String other) {
        if (i == 1) {
            System.out.println("Move 1 from " + from + " to " + to);
        } else {
            //先把第i-1个圆盘从from移动到other上去          //至于0~i-2个圆盘怎么移动由子过程决定
            func(i - 1, from, other, to);
            //再将最下面第i个圆盘移动到to位置
            System.out.println("Move " + i + " from " + from + " to " + to);
            //最后将第i-1个圆盘从other移动到to位置
            func(i - 1, other, to, from);
        }
    }
/**
 * 单词所有子序列
 */
public static void printAllSubsequence1(String s) {
        char[] chars = s.toCharArray();
        List<String> list = new ArrayList<>();
        process1(chars, 0, list);
        System.out.println(list);

    }

    private static void process1(char[] str, int i, List<String> res) {
        if (i == str.length) {
            res.add(String.valueOf(str));
        }
        boolean[] isVisit = new boolean[26];
        for (int j = i; j < str.length; j++) {    //在i及后面位置选一个放到i位置,后面自由排列
            //      分支限界
            if (! isVisit[str[j] - 'a']) {
                isVisit[str[j] - 'a'] = true;   //去重
                swap(i, j, str);
                process1(str,i+1,res);
                swap(i, j, str);
            }
        }

    }
    private static void swap(int i, int j, char[] str) {
        char temp = str[i];
        str[i] = str[j];
        str[j] = temp;
    }

    public static void printAllSubsequence2(String s) {
        char[] chars = s.toCharArray();
        process2(chars, 0);
    }
    private static void process2(char[] str, int i) {
        if (i == str.length) {
            System.out.println(String.valueOf(str));  //String.valueOf();将其他类型变量转为字符串
            return;   //base case 别因为打印 忘了要return!!!
        }
        process2(str, i + 1);  //保留当前字母
        char temp = str[i];
        str[i] = 0;
        process2(str, i + 1);  //不保留当前字母
        str[i] = temp;
    }

/**
 * 两个绝顶聪明的人,从左右两边拿牌,求获胜者最大值
 */
public class CardsInLIne {
    public static int win1(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));
    }
    //先手函数      玩家A
    private static int f(int[] arr, int l, int r) {
        if (l == r) return arr[l];
        // 选择后手函数中最大的   拿走左边的                 拿走右边的
        return Math.max(arr[l] + s(arr, l + 1, r), arr[r] + s(arr, l, r - 1));
    }

    //后手函数      玩家B
    private static int s(int[] arr, int l, int r) {
        if (l == r) return 0;  //只剩一张牌,但因为自己是后手,因此没牌可拿了,返回0
        //轮到自己先手了,因为绝顶聪明,因此之前先手的人会让后手的人拿到最少
        return Math.min(f(arr, l + 1, r), f(arr, l, r - 1));
    }
    
/**
 * 1和A对应,2和B对应,3和C对应
 * "111"  可以转换为AAA、AK、KA
 */
public class NumToStr {
    public static int numToStr(String num) {
        char[] chs = num.toCharArray();
        return process(0, chs);
    }
    //返回值:此时来到i位置,i-1上做的决定所构成的结果
    private static int process(int i, char[] str) {
        if (i == str.length) return 1;  //之前做的决定 整体构成一个有效结果
        //之前做的决定导致在i位置上无法做决定,整体无效
        if (str[i] == '0') {
            return 0;
        }
        if (str[i] == '1') {
            //i位置单独转换
            int res = process(i + 1, str); //i+1可能等于数组长度,i+1=len 符合第一个if
            if (i + 1 < str.length) {  //如果i+1小于len,那么i+2就有结果
                //i与i+1位置合并一起转换
                res += process(i + 2, str);
            }
            return res;
        }
        if (str[i] == '2') {
            //i位置单独转换
            int res = process(i + 1, str);
            //i与i+1位置合并一起转换,且0<str[i+1]<6时才合法
            if (i + 1 < str.length && str[i + 1] <= '6') {
                res += process(i + 2, str);
            }
        }
        //i位置数>=3,只能单独转化
        return process(i + 1, str);
    }
    //dp版本
     public 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;          //从N位置出发有多少种可能性
        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];
    }
    
    								//0~i-1位置确定的情况下,i位置如何选择
    /**
 * 给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表i号物品的重量和价值。
 * 给定一个正数bag,表示一个载重bag的袋子,你装的物品不能超过这个重量。返回你能装下最多的价值是多少?
 */
public class MaxValue {
    public static int maxValue(int[] weights, int[] values, int bag) {
        return process1(0, 0, weights, values, bag);
    }
    //返回值:i...的货物自由选择,返回在该区间可以获得的最大价值
    private static int process1(int i, int alreadyWeights, int[] weights, int[] values, int bag) {
        if (alreadyWeights >= bag) return 0;
        if (i == values.length) return 0;
        return Math.max(
                //要该商品
                values[i] + process1(i + 1, alreadyWeights + weights[i], weights, values, bag),
                //不要该商品
                process1(i + 1, alreadyWeights, weights, values, bag)
        );
    }
    
    public class leetcode39 {
    public static List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> already = new ArrayList<>();
        dfs(0, already, res, candidates, target);
        return res;
    }
        
/**
 * 给你一个 无重复元素 的整数数组candidates 和一个目标整数target,找出candidates中可以使数字和为目标数target的所有不同组合 ,并以列表形式返回。
 * 你可以按 任意顺序返回这些组合。
 * candidates 中的同一数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
 */
        
    public static List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> already = new ArrayList<>();
        dfs(0, already, res, candidates, target);
        return res;
    }
        //already:已经组合的列表
    private static void dfs(int i, List<Integer> already, List<List<Integer>> res, int[] candidates, int target) {
        //超过了直接返回
        if (i == candidates.length) {
            return;
        }
        if (target == 0) {
            //      将already加入到新数组中  不能直接add(already),非基本类型传入的是引用,会在原列表上修改,因此要复制一份
            res.add(new ArrayList<Integer>(already));
            return;
        }
        //直接跳过
        dfs(i + 1, already, res, candidates, target);
        //选择当前值
        if (target >= candidates[i]) {
            already.add(candidates[i]);
            //重复利用,还停留在i位置
            dfs(i, already, res, candidates, target - candidates[i]);
            //加入后重新移除该元素,使该元素可重复使用
            already.remove(already.size() - 1);
        }
    }

动态规划 P17

  • 暴力递归try -> 记忆化搜索(DP)-> 严格表结构(DP) -> 严格表精版(DP)

    • 严格表结构 一定要画图!!!!!!!

      • 描述可变范围,暴力递归中有几个可变参数就是几维表
      • 标出需要计算的终止位置
      • 标出不用计算直接出答案的位置 base case,先把已知位置填好
      • 分析普通位置的依赖关系,并根据依赖关系、已知数据以及终止位置,得出填表策略,确定填表顺序
      • 顺序确定后,将递归调用返回结果改为填表
      • 注意填表过程中可能出现的越界问题
    • 范围尝试的表,左下半区一律无效(即左边界不能大于右边界)、已知值base case一般为对角线

    • 在进行斜率优化时,只需从表中数据找规律即可,没必要回溯原题意

    • 可变参数越少越好(表的维度),单可变参数的维度(一个点(即一个整数是最好的)或一个数组等)

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ySl2urta-1665234185480)(C:\Users\DFL\AppData\Roaming\Typora\typora-user-images\image-20221006183417888.png)]

/**
 * 机器人走路问题,暴力递归 vs 记忆化搜索
 */
public class RobotWalk {
    //                          N个位置   结束    开始    走k步
    public static int robotWalk1(int N, int E, int S, int K) {
        return f1(N, E, S, K);
    }

    private static int f1(int N, int E, int cur, int rest) {
        if (rest == 0) {
            return cur == E ? 1 : 0;
        }
        if (cur == 1) {
            return f1(N, E, 2, rest - 1);
        }
        if (cur == N) {
            return f1(N, E, N - 1, rest - 1);
        }
        return f1(N, E, cur - 1, rest - 1) + f1(N, E, cur + 1, rest - 1);
    }

    //记忆化搜索
    public static int robotWalk2(int N, int E, int S, int K) {
        int[][] dp = new int[N + 1][K + 1];
        for (int i = 0; i <= N; i++) { //都要带等号,初始全部变成 -1
            for (int j = 0; j <= K; j++) {
                dp[i][j] = - 1;
            }
        }
        return f2(N, E, S, K, dp);

    }

    private static int f2(int N, int E, int cur, int rest, int[][] dp) {
        //判断是否有缓存,如果有 直接走缓存return
        if (dp[cur][rest] != - 1) {  //-1意味着没计算过
            return dp[cur][rest];
        }
        //建缓存过程
        if (rest == 0) {       //没剩余步数了,到终点为1,否则为0
            dp[cur][rest] = cur == E ? 1 : 0;
            return dp[cur][rest];
        }
        //还有剩余步数rest>0
        if (cur == 1) {
            dp[cur][rest] = f2(N, E, 2, rest - 1, dp);
        } else if (cur == N) {
            dp[cur][rest] = f2(N, E, N - 1, rest - 1, dp);

        } else {
            dp[cur][rest] = f2(N, E, cur - 1, rest - 1, dp) + f2(N, E, cur + 1, rest - 1, dp);
        }
        return dp[cur][rest];
    }
    
/**
 * 组成aim金额所需要的最少货币数量
 */
 public class MinCoins {
    public static int minCoins1(int[] arr, int aim) {										//暴力递归
        return f1(arr, 0, aim);
    }
    //index。。。后面硬币自由选择,组成rest金额所需最少硬币数
    private static int f1(int[] arr, int index, int rest) {
        if (rest < 0) return - 1;   //没希望,之前决策就错了,无效解
        if (rest == 0) return 0; //够了,不用选了   return 0为有效解
        //rest>0
        // 没硬币,但还不够aim
        if (index == arr.length) return - 1;
        //还有硬币,且还不够aim金额
        int p1 = f1(arr, index + 1, rest);  //不选当前硬币
        int p2Next = f1(arr, index + 1, rest - arr[index]); //选当前硬币
        //其中一个有效
        if (p1 == - 1 && p2Next == - 1) return - 1;   //下一步选哪个都没戏,这一步就无效
        if (p1 == - 1) return p2Next + 1;
        if (p2Next == - 1) return p1;
        //都有效
        return Math.min(p1, p2Next + 1);
    }

    public static int minCoins2(int[] arr, int aim) {									//记忆化搜索
        int[][] dp = new int[arr.length + 1][aim + 1];
        for (int i = 0; i <= arr.length; i++) {
            for (int j = 0; j <= aim; j++) {
                dp[i][j] = - 2;  //-2表示没算过
            }
        }
        return f2(arr, 0, aim, dp);
    }

    //index。。。后面硬币自由选择,组成rest金额所需最少硬币数
    private static int f2(int[] arr, int index, int rest, int[][] dp) {
        //dp二维表中没有rest小于0的区域,这里用硬逻辑表示,二维表左侧的区域都是-1,中了无效区的缓存
        if (rest < 0) return - 1; //rest<0
        if (dp[index][rest] != - 2) {  //有缓存直接走缓存
            return dp[index][rest];
        }
        //构建缓存过程
        if (rest == 0) {    //rest=0
            dp[index][rest] = 0;
        } else if (index == arr.length) {  //rest > 0 index=N
            dp[index][rest] = - 1;
        } else {   //rest>0 index<N
            //还有硬币,且 还不够aim金额
            int p1 = f2(arr, index + 1, rest, dp);  //不选当前硬币
            int p2Next = f2(arr, index + 1, rest - arr[index], dp); //选当前硬币
            //其中一个有效
            if (p1 == - 1 && p2Next == - 1) {
                dp[index][rest] = - 1;
            }   //下一步选哪个都没戏,这一步就无效
            else if (p1 == - 1) {
                dp[index][rest] = p2Next + 1;
            } else if (p2Next == - 1) {
                dp[index][rest] = p1;
            } else {
                //都有效
                dp[index][rest] = Math.min(p1, p2Next + 1);
            }
        }
        return dp[index][rest];
    }
    
    //动态规划  严格表依赖  初始化表、填表
    //row 为index  col为rest
    public static int minCoins3(int[] arr, int aim) {									//严格表结构
        int N = arr.length;
        int[][] dp = new int[N + 1][aim + 1];
        //初始化边界过程
        for (int row = 0; row <= N; row++) {
            dp[row][0] = 0;  //每一行的0列为0
        }
        for (int col = 1; col <= aim; col++) { //从第一列开始最后一行的每一列为-1
            dp[N][col] = - 1;
        }
        //一般位置计算过程
        for (int index = N - 1; index >= 0; index--) {
            for (int rest = 1; rest <= aim; rest++) {
                int p1 = dp[index + 1][rest];
                int p2Next = - 1;  //越界则为-1
                if (rest - arr[index] >= 0) {
                    p2Next = dp[index + 1][rest - arr[index]];
                }
                if (p1 == - 1 && p2Next == - 1) {
                    dp[index][rest] = - 1;
                } else if (p1 == - 1) {
                    dp[index][rest] = p2Next + 1;
                } else if (p2Next == - 1) {
                    dp[index][rest] = p1;
                } else {
                    dp[index][rest] = Math.min(p1, 1 + p2Next);
                }
            }
        }
        //0行
        return dp[0][aim];
    }
}

/**
 * 规定步数马到达终点的方法数
 * 2549ms   暴力递归
 * -----------------------------------					三维表						
 * 0ms      严格表结构
 */
public class ChineseChess {
    public static int horseWalk1(int K, int x, int y) {
        return process(K, x, y);
    }

    //从(0,0)位置要去往(x,y)位置,还需跳step步           要去往的地方
    //也可以这样想,从(x,y)位置跳往(0,0)位置
    public static int process(int step, int x, int y) {
        if (x < 0 || x > 8 || y < 0 || y > 9) {
            return 0;
        }
        if (step == 0) {  //剩余0步,如果要跳往的位置为(0,0)就是一种合理的方法
            return (x == 0 & y == 0) ? 1 : 0;
        }
        return + horseWalk1(step - 1, x - 1, y - 2)
                + horseWalk1(step - 1, x - 1, y + 2)
                + horseWalk1(step - 1, x - 2, y - 1)
                + horseWalk1(step - 1, x - 2, y + 1)
                + horseWalk1(step - 1, x + 1, y - 2)
                + horseWalk1(step - 1, x + 1, y + 2)
                + horseWalk1(step - 1, x + 2, y - 1)
                + horseWalk1(step - 1, x + 2, y + 1);
    }

    public static int horseWalk2(int step, int x, int y) {
        if (x < 0 || x > 8 || y < 0 || y > 9 || step < 0) {
            return 0;
        }
        int[][][] dp = new int[step + 1][9][10];
        dp[0][0][0] = 1;
        //0层不管。只有原点是1,其余都是0(初始默认)
        for (int h = 1; h <= step; h++) {  //层
            for (int r = 0; r < 9; r++) {               //枚举所有可能性  同一层不存在依赖关系
                for (int c = 0; c < 10; c++) {
                    dp[h][r][c] += getValue(dp, h - 1, r - 1, c - 2);       //getValue();防止越界
                    dp[h][r][c] += getValue(dp, h - 1, r - 1, c + 2);
                    dp[h][r][c] += getValue(dp, h - 1, r - 2, c - 1);
                    dp[h][r][c] += getValue(dp, h - 1, r - 2, c + 1);
                    dp[h][r][c] += getValue(dp, h - 1, r + 1, c - 2);
                    dp[h][r][c] += getValue(dp, h - 1, r + 1, c + 2);
                    dp[h][r][c] += getValue(dp, h - 1, r + 2, c - 1);
                    dp[h][r][c] += getValue(dp, h - 1, r + 2, c + 1);
                }
            }
        }
        return dp[step][x][y];
    }

    private static int getValue(int[][][] dp, int h, int r, int c) {
        if (r < 0 || r > 8 || c < 0 || c > 9 ) {
            return 0;
        }
        return dp[h][r][c];
    }
    
    /**
 * 每个纸币自由选择,可重复选,aim金额找零的方法数
 * 3937256          暴力
 * 892ms
 * -----------------------------------
 * 3937256          严格表结构,枚举版
 * 3ms
 * -----------------------------------
 * 3937256          严格表结构,去枚举版、斜率优化,只和观察有关,不需要在意原题意,咱就做实在人
 * 0ms
 */
public class CoinsWays {

    public static int way1(int[] arr, int aim) {
        return process1(arr, 0, aim);
    }

    private static int process1(int[] arr, int index, int rest) {
        if (index == arr.length) {
            return rest == 0 ? 1 : 0;
        }
        int ways = 0;
        //枚举每种面值纸币使用张数,但总金额不要超过rest  2元使用0、1、2、3、4。。。 5元使用0、1、2、3、4、5
        for (int zhang = 0; arr[index] * zhang <= rest; zhang++) {
            ways += process1(arr, index + 1, rest - arr[index] * zhang);  //前面面值搞定了后还剩rest - arr[index] * zhang钱要搞,继续枚举
        }
        return ways;
    }

    public static int way2(int[] arr, int aim) {
        if (arr == null || arr.length == 0) return 0;
        int N = arr.length;
        int[][] dp = new int[N + 1][aim + 1];
        dp[N][0] = 1;
        for (int index = N - 1; index >= 0; index--) {    //从下之上填表
            for (int rest = 0; rest <= aim; rest++) {
                int ways = 0;
                for (int zhang = 0; arr[index] * zhang <= rest; zhang++) {   //根据依赖关系填表
                    ways += dp[index + 1][rest - arr[index] * zhang];  //前面面值搞定了后还剩rest - arr[index] * zhang钱要搞,继续枚举
                }
                dp[index][rest] = ways;  //当前层依赖它下一层的某些值
            }
        }
        return dp[0][aim];
    }

    //优化枚举行为  枚举行为中,当前位置依赖其下一行 rest - arr[index] * zhang(zhang++) 列的值 a+b+c+d+...
    // 但观察发现当前行rest-1*arr[index]位置的值是b+c+d得来的,因此当前格子的值就是其下一行同一列的值+同一行、当前列值减一个当前面值的列的值
    public static int way3(int[] arr, int aim) {
        if (arr == null || arr.length == 0) return 0;
        int N = arr.length;
        int[][] dp = new int[N + 1][aim + 1];
        dp[N][0] = 1;
        for (int index = N - 1; index >= 0; index--) {
            for (int rest = 0; rest <= aim; rest++) {
                dp[index][rest] = dp[index + 1][rest];//依赖下方的格子
                if (rest - arr[index] >= 0) {
                    dp[index][rest] += dp[index][rest - arr[index]];  //以及当前行中,当前列值减一个当前面值的列
                }
            }
        }
        return dp[0][aim];
    }
    

基础提升

Manacher算法

  • 回文半径数组可以帮助解决很多回文问题
/**												i’回文串小于R-i  i位置回文半径与i‘相等
 * 求最大回文子串长度				i在R内  i在R外   i’回文串大于R-i	 i位置回文半径等于R-i
 *												i’回文串等于R-i  i位置回文半径起码时R-i,从i位置可能需要扩
 */
public static int maxLcpsLength(String s) {							
    if (s == null || s.length() == 0) return 0;
    char[] str = manacherString(s);
    int[] pArr = new int[str.length];  //和处理串长度相同
    int C = - 1; //中心
    int R = - 1; //R为回文右边界再往右的位置,最右的有效区域为R-1
    int max = Integer.MIN_VALUE;  //阔出来的最大值
    for (int i = 0; i < str.length; i++) {	
        //i至少的回文区域,先给pArr[i]
        //分i在R内,与i在R外      选回文半径较小的  pArr[i'],i到R的距离R-i
        pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;  //i在R外,回文区域为自己
		//从需要验证的位置开始
        while (i + pArr[i] < str.length && i - pArr[i] > - 1) {
            //从至少的回文区域都尝试一步,看回文区域能不能变大
            if (str[i + pArr[i]] == str[i - pArr[i]]) {
                pArr[i]++;
            } else {
                break;
            }
        }
        //更新 R、C
        if (i + pArr[i] > R) {
            R = i + pArr[i];
            C = i;
        }
        max = Math.max(max, pArr[i]);
    }
    return max - 1;  //处理后的字符串最大回文半径减1,就是原字符串的最大回文长度
}
//	accb		#a#c#c#b#   	因为在回文串长度为偶数时、回文中心线在两个数字中间,若从每个位置i出发往两边扩,会错过一些内容 accb  找不到cc
private static char[] manacherString(String str) {
    char[] charArr = str.toCharArray();
    char[] res = new char[2 * str.length() + 1];
    int index = 0;
    for (int i = 0; i < res.length; i++) {
        res[i] = (1 & i) == 0 ? '#' : charArr[index++];
    }
    return res;
}

滑动窗口

  • O(N)的平均时间复杂度下,动态生成窗口中最大值
  • 使用双向队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7G3sNWSN-1665234185481)(C:\Users\DFL\AppData\Roaming\Typora\typora-user-images\image-20221005191724095.png)]

单调栈

  • 解决当前数左右两边离当前数最近的比当前数大的值
  • 使用一个栈(存放的是数组下标),大数在下小数在上,从左往右遍历数组,遇到比栈顶元素大的数时,就依次弹出并生成信息,直至当前数可以放进去为止,每个弹出的数,左边离当前数最大的是它下面压着的数,右边离它最大的是使它们弹出的数即当前数。
  • 若包含相同元素,栈中相同元素放入一个链表中(可以是LInkedList、ArrayList)

Morris遍历

  • 时间复杂度O(N),额外空间复杂度O(1)
  • Morris遍历无法到达同意节点
  • 线索二叉树,使用空闲指针完成遍历,当前节点左树最右节点右指针指向当前节点cur。
  • 先序遍历:如果一个节点只到达一次直接打印,到达两次第一次才打印
  • 中序遍历:如果一个节点只到达一次直接打印,到达两次第二次才打印
  • 后序遍历:如果一个节点能到达两次,第二次打印,然后逆序打印左树的右边界 好复杂 略。。。。。。
public static void morris(TreeNode head) {
    if (head == null) return;
    TreeNode cur = head;
    TreeNode mostRight = null;
    while (cur != null) {
        mostRight = cur.left;
        if (mostRight != null) {   //有左子树
            while (mostRight.right != null && mostRight.right != cur) {  //能够往右走且不回到当前节点
                mostRight = mostRight.right;  //循环结束到达cur左树最右节点
            }
            if (mostRight.right == null) {  //第一次来到cur
                mostRight.right = cur;  //最右节点指针指向当前节点cur
                cur = cur.left;  //cur向左孩子移动
                continue;  //继续进行循环
            } else {    //不是第一次来到当前节点,而是顺着左树最右指针来的  mostRight.right ==cur;
                mostRight.right = null; //将右指针还原回去
            }
        }
        cur = cur.right;  //当前树没有左子树,直接向右移动
    }
}
//先序
public static void morrisPre(TreeNode head) {
        if (head == null) return;
        TreeNode cur = head;
        TreeNode mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {   //有左子树
                while (mostRight.right != null && mostRight.right != cur) {  //能够往右走且不回到当前节点
                    mostRight = mostRight.right;  //循环结束到达cur左树最右节点
                }
                if (mostRight.right == null) {  //第一次来到cur
                    System.out.println(cur.val);  //第一次来打印
                    mostRight.right = cur;  //最右节点指针指向当前节点cur
                    cur = cur.left;  //cur向左孩子移动
                    continue;  //继续进行循环
                } else {    //不是第一次来到当前节点,而是顺着左树最右指针来的  mostRight.right ==cur;
                    mostRight.right = null; //将右指针还原回去
                }
            } else {
                System.out.println(cur.val);  //没有左子树直接打印
            }
            cur = cur.right;  //当前树没有左子树,直接向右移动
        }
    }
//中序
    public static void morrisIn(TreeNode head) {
        if (head == null) return;
        TreeNode cur = head;
        TreeNode mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {   //有左子树
                while (mostRight.right != null && mostRight.right != cur) {  //能够往右走且不回到当前节点
                    mostRight = mostRight.right;  //循环结束到达cur左树最右节点
                }
                if (mostRight.right == null) {  //第一次来到cur
                    mostRight.right = cur;  //最右节点指针指向当前节点cur
                    cur = cur.left;  //cur向左孩子移动
                    continue;  //继续进行循环
                } else {    //不是第一次来到当前节点,而是顺着左树最右指针来的  mostRight.right ==cur;
                    mostRight.right = null; //将右指针还原回去
                }
            }
            System.out.println(cur.val);  //没有左树,或者第二次来到该节点打印
            cur = cur.right;  //当前树没有左子树,直接向右移动
        }
    }
//后序
    public static void morrisPost(TreeNode head) {
        if (head == null) return;
        TreeNode cur = head;
        TreeNode mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {   //有左子树
                while (mostRight.right != null && mostRight.right != cur) {  //能够往右走且不回到当前节点
                    mostRight = mostRight.right;  //循环结束到达cur左树最右节点
                }
                if (mostRight.right == null) {  //第一次来到cur
                    mostRight.right = cur;  //最右节点指针指向当前节点cur
                    cur = cur.left;  //cur向左孩子移动
                    continue;  //继续进行循环
                } else {    //不是第一次来到当前节点,而是顺着左树最右指针来的  mostRight.right ==cur;
                    mostRight.right = null; //将右指针还原回去
                    printEdge(cur.left);  //第二次来到这个节点,逆序打印左树右边界(相当与链表逆序过程)
                }
            }
            printEdge(head);    //跑完后单独打印整棵树的右边界
            cur = cur.right;  //当前树没有左子树,直接向右移动
        }
    }
    private static void printEdge(TreeNode left) {
        }

//Morris中序遍历改造
    public static boolean isValidBST(TreeNode head) {
        if (head == null) return true;
        TreeNode cur = head;
        TreeNode mostRight = null;
        int preValue = Integer.MIN_VALUE;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {   //有左子树
                while (mostRight.right != null && mostRight.right != cur) {  //能够往右走且不回到当前节点
                    mostRight = mostRight.right;  //循环结束到达cur左树最右节点
                }
                if (mostRight.right == null) {  //第一次来到cur
                    mostRight.right = cur;  //最右节点指针指向当前节点cur
                    cur = cur.left;  //cur向左孩子移动
                    continue;  //继续进行循环
                } else {    //不是第一次来到当前节点,而是顺着左树最右指针来的  mostRight.right ==cur;
                    mostRight.right = null; //将右指针还原回去
                }
            }
//            System.out.println(cur.val);  //没有左树,或者第二次来到该节点打印
            if (cur.val <= preValue) {
                return false;
            }
            preValue = cur.val;
            cur = cur.right;  //当前树没有左子树,直接向右移动
        }
        return true;
    }

有序表

log(N)时机复杂度

  • 红黑树
  • AVL树
  • SB树
  • 跳表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kgvGIeY0-1665234185481)(C:\Users\DFL\AppData\Roaming\Typora\typora-user-images\image-20221007004239373.png)]

不用大于24是因为,24是6、8最小公倍数,能用三个8号袋子装何必要用4个6号袋子装,比如剩27个,不管用哪个袋子装,最后都会剩3个,何必要用6号袋子,并且这个剩余3个苹果的情况在多使用3个8号袋子时也计算过了。

打表法YYDS

网上那些写数学题解的都是垃圾货色,都是打表看出规律后再想的数学解释,就是为了装逼,让你佩服他

/**
 * 牛羊每次吃4的整数幂的草,谁最后吃谁赢
 */
public class Winner {
    public static String winner1(int n) {
        if (n < 5) {
            return (n == 0 || n == 2) ? "后手" : "先手";
        }
        int base = 1;  //先手决定吃的草
        while (base >= 0) {
            if (winner1(n - base).equals("后手")) {
                return "先手";
            }
            if (base > n / 4) {
                break;
            }
            base *= 4;
        }
        return "后手";
    }
    public static String winner2(int n) {
        if (n % 5 == 0 || (n - 2) % 5 == 0) {
            return "后手";
        }
            return "先手";
    }
    public static void main(String[] args) {
        for (int i = 0; i <= 50; i++) {
//            System.out.println(i + " : " + winner1(i));
            System.out.println(i + " : " + winner2(i));
        }
    }
}

提升班习题课

  • 表格预处理

  • 根据一个随机函数,返回另一个随机函数

/**
 * 给定一个函数f,可以1~5的数字等概率返回一个。请加工出1~7的数字等概率返回一个的函数g。
 * 给定一个函数f,可以a~b的数字等概率返回一个。请加工出c~d的数字等概率返回一个的函数g。
 * 给定一个函数f,以p概率返回0,以1-p概率返回1。请加工出等概率返回0和1的函数g
 */
public class Random {
    //以1~5的数字等概率返回一个
    public static int f() {
        return 0;  //空实现
    }

    //0、1随机发生器
    public static int g01() {
        int res = 0;
        do {
            res = f();
        } while (res == 3);
        return res < 3 ? 0 : 1;
    }

    //准备3个二进制位
    public static int g17() {
        int res = 0;
        do {
            res = g01() << 2 + g01() << 1 + g01();  //随机返回0~6
        } while (res == 7);
        return res + 1;
    }

    //以13~21数字等概率返回一个
    public static int r() {
        return 0;  //空实现
    }

    //0、1随机发生器
    public static int r01() {
        int res = 0;
        do {
            res = r();
        } while (res == 21);
        return res < 17 ? 0 : 1;  //二分
    }

    //准备3个二进制位
    public static int g3059() {
        int res = 0;
        do {
            for (int i = 0; i < 5; i++) {
                res += (r() << i); //随机返回30~59  0~29
            }
        } while (res > 29);
        return res + 30;
    }


    //以p概率返回0,以1-p概率返回1。
    public static int fp() {  //调两次共四个结果 00、01、10、11  其中01、10为等概率事件
        return 0;   //空实现
    }

    public static int gp() {
        int res1 = - 1;
        int res2 = - 1;
        do {
            res1 = fp();
            res2 = fp();
        } while ((res1 ^ res2) == 0);
        return res1 == 0 ? 0 : 1;
    }
}
/**
 * 判断括号是否合法字符串		//变种题:求括号最大深度depth = 1()()  depth = 2(())  记录过程中count出现的最大值即可
 */
public class LegalBracket {
    public static boolean checkBracket(String s) {
        if (s == null || s.length() == 0) return true;
        int count = 0;
        for (int i = 0; i < s.length(); i++) {      //左括号数量>=右括号数量 每个右括号都有左括号可配
            if (count < 0) return false;
            count = s.charAt(i) == '(' ? count + 1 : count - 1;
        }
        return count == 0;
    }
/**
 * 添加多少括号可以让字符串括号合法
 */
    public static int addBracket(String s) {
        int count = 0;
        int ans = 0;
        for (int i = 0; i < s.length(); i++) {      //左括号数量>=右括号数量 每个右括号都有左括号可配
            if (s.charAt(i) == '(') {
                count++;
            } else {            //遇到了右括号,如果此时count==0,说明原来刚好够,所以加上现在这个后需要补一个括号,即ans++;
                if (count == 0) { //count不加了
                    ans++;          //补个左括号
                } else {
                    count--;
                }
            }
        }
        //count 含义 左括号有多少没对上,ans含义 左括号缺了几个
        return ans+count; // count>=0 ((((((()))  count = 4 ,ans = 0; ((())))))) count = 0,ans = 4;
    }
}

/**
 * n个节点的二叉树数量所包含的结构种类个数
 */
    public static int numsTres1(int n) {
        return process1(n);
    }

    private static int process1(int n) {
        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 = process1(leftNum);
            int rightWays = process1(n - 1 - leftNum);
            res += leftWays * rightWays;
        }
        return res;
    }

    //dp
    public static int numsTres2(int n) {
        if (n < 2) return 1;
        int[] dp = new int[n + 1]; //0~n;
        dp[0] = 1;  //0个节点,空树
        for (int i = 1; i <= n; i++) {  //枚举总的节点个数,0、1、2、3.。。。。n个数为多少
            for (int j = 0; j <= i-1; j++) {  //枚举左孩子数量
                dp[i] += dp[j] * dp[i - j - 1];
            }
        }
        return dp[n];
    }

/**
 * 集合a、b 可以从任一集合中拿出一元素x放入另一个集合中,两个集合平均值都变大,进行此magic的操作次数最多
 */
public class MagicOps {
    //  a、b平均值a'、b' 且 a'>b'  从a中拿 x满足 b'<x<a'且x在b中不存在  拿走满足条件的最小元素(吊车尾)
    public static int maxOps(int[] arr1, int[] arr2) {
        double sum1 = 0;
        for (int i = 0; i < arr1.length; i++) {
            sum1 += (double) arr1[i];
        }
        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)) {        //重定位,可以不用写一堆if-else
            arrMore = arr1;
            sumMore = sum1;
            arrLess = arr2;
            sumLess = sum2;
        } else {
            arrMore = arr2;
            sumMore = sum2;
            arrLess = arr1;
            sumLess = sum1;
        }
        Arrays.sort(arrMore);  //依次选出大于平均值的最小值
        HashSet<Integer> setLess = new HashSet<>();  //登记一下,保证所加元素在小集合中没出现过,出现过将导致加入后小集合平均值不变
        for (int i : arrLess) {
            setLess.add(i);
        }
        int moreSize = arrMore.length;  //平均值大的集合还剩几个元素
        int leseSize = arrLess.length;  //平均值小的集合还剩几个元素
        int ops = 0; //操作了几次
        for (int i = 0; i < arrMore.length; i++) {
            double cur = (double) arrMore[i];
            if (cur < avg(sumMore, moreSize) && cur > avg(sumLess, leseSize)
                    && ! setLess.contains(cur)) {
                sumMore -= cur;
                moreSize--;
                sumLess += cur;
                leseSize++;
                setLess.add(arrMore[i]);  //set泛型为整型
                ops++;

            }
        }
        return ops;
    }

    private static double avg(double sum, int size) {
        return (double) (sum / size);
    }
}

/**
 * 最长合法括号子串					leetcode32题,有难度,多画图。
 */
public class LongestValidParentheses {
    //子串必须由当前字符结尾,最长有效长度是多少?  dp[i]含义
    public static int longestValidParentheses(String s) {
        if (s == null || s == "") return 0;
        char[] str = s.toCharArray();
        int[] dp = new int[s.length() + 1];
        int res = 0;
        int pre = 0;
        for (int i = 1; i < s.length(); i++) {      //i=0位置必是0,不用算
            if (str[i] == ')') {       //必须由右括号结尾才进行
                //    i前一个位置,根据该位置的值往前数dp[i-1]个长度(该位置算出的有效长度)
                pre = i - dp[i - 1] - 1;    //这个位置不是左括号就肯定是0(( {)} { 这一坨为i-1结尾有效长度 }  ) 如果第一个大括号位置不是'(' 肯定会把第二个大括号有效长度变大
                if (pre >= 0 && str[pre] == '(') {
                    //      起码是2这个长度,再加上dp[i-1]前面i-1位置算过的长度,
                    //      还有可能更多 {(())}(()),后面大括号位置就是pre-1
                    dp[i] = dp[i - 1] + 2 + (pre > 0 ? dp[pre - 1] : 0);
                }
            }
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

/**
 *使用一个辅助栈完成原始栈的排序:栈顶到栈底从大到小。
 *允许使用一个辅助栈,可以申请变量,但不能使用其他数据结构。
 *解题思路:辅助栈一直维持由上至下从小到大  ,最终结果由大到小
 */
public class StackSort {
    public static void sort(Stack<Integer> stack) {
        Stack<Integer> help = new Stack<>();
        help.push(stack.pop());
        int cur = 0;
        while (! stack.isEmpty()) {
            cur = stack.pop();  //记住弹出这个值
            while (! help.isEmpty() && cur > help.peek()) {  //辅助栈一直维持由上至下从小到大
                stack.push(help.pop());
            }
            help.push(cur);     //弹够了,可以压入了
        }
        while (! help.isEmpty()) {
            stack.push(help.pop());
        }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QHc6jmV9-1665234185482)(C:\Users\DFL\AppData\Roaming\Typora\typora-user-images\image-20221008153839135.png)]

数组矩阵调整问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B9a0kGBC-1665234185482)(C:\Users\DFL\AppData\Roaming\Typora\typora-user-images\image-20221008154247851.png)]

左上角下标、右上角下标, 沿对角线向内移动,错过去时停止

螺旋打印矩阵

image-20221008155202857

分组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUlxZ19N-1665234185483)(C:\Users\DFL\AppData\Roaming\Typora\typora-user-images\image-20221008161002688.png)]
arrLess = arr2;
sumLess = sum2;
} else {
arrMore = arr2;
sumMore = sum2;
arrLess = arr1;
sumLess = sum1;
}
Arrays.sort(arrMore); //依次选出大于平均值的最小值
HashSet setLess = new HashSet<>(); //登记一下,保证所加元素在小集合中没出现过,出现过将导致加入后小集合平均值不变
for (int i : arrLess) {
setLess.add(i);
}
int moreSize = arrMore.length; //平均值大的集合还剩几个元素
int leseSize = arrLess.length; //平均值小的集合还剩几个元素
int ops = 0; //操作了几次
for (int i = 0; i < arrMore.length; i++) {
double cur = (double) arrMore[i];
if (cur < avg(sumMore, moreSize) && cur > avg(sumLess, leseSize)
&& ! setLess.contains(cur)) {
sumMore -= cur;
moreSize–;
sumLess += cur;
leseSize++;
setLess.add(arrMore[i]); //set泛型为整型
ops++;

        }
    }
    return ops;
}

private static double avg(double sum, int size) {
    return (double) (sum / size);
}

}

/**

  • 最长合法括号子串 leetcode32题,有难度,多画图。
    */
    public class LongestValidParentheses {
    //子串必须由当前字符结尾,最长有效长度是多少? dp[i]含义
    public static int longestValidParentheses(String s) {
    if (s == null || s == “”) return 0;
    char[] str = s.toCharArray();
    int[] dp = new int[s.length() + 1];
    int res = 0;
    int pre = 0;
    for (int i = 1; i < s.length(); i++) { //i=0位置必是0,不用算
    if (str[i] == ‘)’) { //必须由右括号结尾才进行
    // i前一个位置,根据该位置的值往前数dp[i-1]个长度(该位置算出的有效长度)
    pre = i - dp[i - 1] - 1; //这个位置不是左括号就肯定是0(( {)} { 这一坨为i-1结尾有效长度 } ) 如果第一个大括号位置不是’(’ 肯定会把第二个大括号有效长度变大
    if (pre >= 0 && str[pre] == ‘(’) {
    // 起码是2这个长度,再加上dp[i-1]前面i-1位置算过的长度,
    // 还有可能更多 {(())}(()),后面大括号位置就是pre-1
    dp[i] = dp[i - 1] + 2 + (pre > 0 ? dp[pre - 1] : 0);
    }
    }
    res = Math.max(res, dp[i]);
    }
    return res;
    }
    }

/**
*使用一个辅助栈完成原始栈的排序:栈顶到栈底从大到小。
*允许使用一个辅助栈,可以申请变量,但不能使用其他数据结构。
*解题思路:辅助栈一直维持由上至下从小到大 ,最终结果由大到小
*/
public class StackSort {
public static void sort(Stack stack) {
Stack help = new Stack<>();
help.push(stack.pop());
int cur = 0;
while (! stack.isEmpty()) {
cur = stack.pop(); //记住弹出这个值
while (! help.isEmpty() && cur > help.peek()) { //辅助栈一直维持由上至下从小到大
stack.push(help.pop());
}
help.push(cur); //弹够了,可以压入了
}
while (! help.isEmpty()) {
stack.push(help.pop());
}
}


[外链图片转存中...(img-QHc6jmV9-1665234185482)]

### 数组矩阵调整问题

[外链图片转存中...(img-B9a0kGBC-1665234185482)]

左上角下标、右上角下标, 沿对角线向内移动,错过去时停止

螺旋打印矩阵

<img src="C:\Users\DFL\AppData\Roaming\Typora\typora-user-images\image-20221008155202857.png" alt="image-20221008155202857" style="zoom:50%;" />

分组

[外链图片转存中...(img-jUlxZ19N-1665234185483)]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值