左神数据结构与算法(中级提升)——02

题目十一:二叉树递归套路

二叉树的每个节点都有一个int型权值,给定一棵二叉树,要求计算出从根节点到叶节点的所有路径中,权值和 最大的值为多少。

package class02;

public class Code07_BSTRecursiveFormula {

    static class Node{
        public int value;
        public Node left;
        public Node right;

        public Node(int value){this.value = value;}
    }

    //方法一:全局变量,只有在达到叶子节点的时候,才会进行修改
    public static int maxSum = Integer.MIN_VALUE;

    public static int maxPath(Node head){
        process(head,0);
        return maxSum;
    }

    //pre 表示根节点到当前节点x上方的路径和
    public static void process(Node x,int pre){
        if(x.left == null && x.right == null){
            maxSum = Math.max(maxSum,pre + x.value);
        }
        if(x.left != null){
            process(x.left,pre + x.value);
        }
        if(x.right != null){
            process(x.right,pre + x.value);
        }
    }
    
    //方法二:使用递归套路  将要得到的信息进行包装
    public static int maxPath2(Node head){
        if(head == null) return 0;
        return p(head).maxPath;
    }
    
    static class ReturnType{
        public int maxPath;
        public ReturnType(int maxSum){this.maxPath = maxSum;}
    }
    
    //当前以x为头节点的权值和最大
    public static ReturnType p(Node x){
        if (x.left == null && x.right == null){
            return new ReturnType(x.value);
        }
        //获得左右子树的最大路径和
        int leftMaxPath = (x.left == null) ? Integer.MIN_VALUE : p(x.left).maxPath;
        int righrMaxPath = (x.right == null) ? Integer.MIN_VALUE : p(x.right).maxPath;
        //获得当前树的最大路径和
        int maxPath = Math.max(leftMaxPath,righrMaxPath) + x.value;
        return new ReturnType(maxPath);
    }

}

题目十二:二维数组规律寻找

给定一个元素为非负整数的二维数组matrix,每行和每列都是从小到达排序的。

再给定一个非负整数aim,请判断aim是否在matrix中。

package class02;

import org.junit.Test;

/**
 * 给定一个元素为非负整数的二维数组matrix,每行和每列都是从小到达排序的。
 * 再给定一个非负整数aim,请判断aim是否在matrix中。
 */
public class Code08_SearchInDoubleArray {

    //从右上角开始查询  查询最多一行+一列
    // arr 行列是从小到大的 非负整数
    public static boolean isInDoubleArray(int[][] arr,int num){
        if(arr == null || num < 0){
            return false;
        }
        int N = arr.length;
        int M = arr[0].length;
        int row = 0;
        int col = M - 1;  //初始位置在右上角
        while(row < N && col >= 0){
            int cur = arr[row][col];
            if(cur > num){
                col--;
            }else if(cur < num) {
                row++;
            }else {
                return true;
            }
        }
        return false;
    }
}

对于一个只有01的二维数组,0始终在1的左边,返回具有1最多的行。

package class02;

import java.util.ArrayList;
import java.util.List;

/**
 * 对于一个只有01的二维数组,0始终在1的左边,返回具有1最多的行。
 */
public class Code09_SearchMaxOne {

    public static List<Integer> getMaxOne(int[][] arr){
        int N = arr.length;
        int M = arr[0].length;
        int row = 0;
        int col = M - 1;  //初始化位置在左上角
        List<Integer> list = new ArrayList<>();
        int maxSize = 0;
        while (row < N && col >= 0){
            int cur = maxSize;
            if(arr[row][col] == 1) {
                while (col - 1 >= 0 && arr[row][col - 1] == 1) {
                    col--;
                    maxSize++;
                }
                //如果最大值变化,则需要把原来的最大值行数全部清空
                //如果最大值没有变化,则直接添加新的行就可
                if(maxSize != cur){
                    list.clear();
                }
                list.add(row);
            }
            row++;
        }
        return list;
    }
}

题目十三:洗衣机问题、打包问题(看当前i位置情况)

有n个打包机器从左到右一字排开,上方有一个自动装置会抓取一批放物品到每个打包机上,放到每个机器上的这些物品数量有多有少,由于物品数量不同,需要工人将每个机器上的物品移动从而达到物品数量下相等才能打包。每个物品重量太大、每次只能搬一个物品进行移动,为了省力,只在相邻的机器上移动。请计算在搬动最小轮数的前提下,使每个机器上的物品数量相等。如果不能使每个机器上的物品相同,返回-1。

eg:[1,0,5]表示有三个机器,每个机器上分别有1,0,5个物品,经过这些轮后:

第一轮:1 0 <- 5 ==> 1 1 4

第二轮:1 <- 1 <- 4 ==> 2 1 3

第三轮:2 1 <- 3 ==> 2 2 2

移动了三轮,每个机器上的物品相等,所以返回3

eg:[2,2,3]表示有3个机器,每个机器上分别有2、2、3个物品,这些物品不管怎么移动,都不能使三个机器上物品数量相等,返回-1。

只需要看当前位置左边和右边的衣服总数

package class03;

import org.junit.Test;

import java.util.Arrays;

/**
 * eg:[2,2,3]表示有3个机器,每个机器上分别有2、2、3个物品,
 * 这些物品不管怎么移动,都不能使三个机器上物品数量相等,返回-1。
 */
public class Code01_AveragePackage {

    //arr中只有相邻的两个才可以搬运
    public int minTimesOfPackage(int[] arr){
        if(arr == null || arr.length == 0){
            return 0;
        }
        int sum = 0;
        for(int i : arr){
            sum += i;
        }
        if(sum % arr.length != 0){
            return -1;
        }
        int avg = sum / arr.length;
        int maxTimes = Integer.MIN_VALUE;
        int leftSum = 0;
        int rightSum = sum - arr[0];
        for(int i = 0;i < arr.length;i++){
            int leftPoor = leftSum - avg * i;   //i位置左侧衣服总和的差值
            int rightPoor = rightSum - avg * (arr.length - 1 - i); //右侧衣服总和的差值
            int curTimes = 0;
            if(leftPoor < 0 && rightPoor < 0){  //左负右负  i位置每次只能拿一个
                curTimes = Math.abs(leftPoor) + Math.abs(rightPoor);
            }else if((leftPoor < 0 && rightPoor > 0)
                        || (leftPoor > 0 && rightPoor < 0)){  //左负右正 或者 左正右负
                curTimes = Math.max(Math.abs(leftPoor),Math.abs(rightPoor));
            }else if(leftPoor > 0 && rightPoor > 0){  //左正右正
                curTimes = Math.max(leftPoor,rightPoor);
            }else {  //左0 右0
                curTimes = 0;
            }
            maxTimes = Math.max(maxTimes,curTimes);  //取每次情况最差的
            leftSum += arr[i];  //向后移动
            rightSum -= arr[i];
        }
        return maxTimes;
    }

    public int[] getRandomArr(){
        int N = (int)(Math.random() * 20) + 1;  //长度1~20
        int[] arr = new int[N];
        for (int i = 0;i < N;i++){
            arr[i] = (int)(Math.random() * 101);  //个数为0~100
        }
        return arr;
    }

    @Test
    public void test(){
        int N = 20;
        for(int i = 0;i < N;i++){
            int[] arr = getRandomArr();
            int min = minTimesOfPackage(arr);
            System.out.println(Arrays.toString(arr));
            System.out.println(min);
        }
    }
}

题目十四:宏观调控-Zigzag打印、螺旋打印

用zigzag的方式打印矩阵,比如如下的矩阵

0 1 2 3

4 5 6 7

8 9 10 11

打印顺序为:0 1 4 8 5 2 3 6 9  10 7 11

package class03;

import org.junit.Test;

/**
 * zigzag打印
 */
public class Code02_ZigzagPrint {

    //给两个点,都是在左上角,一个向右,一个向下,打印斜角
    public void zigzagPrint(int[][] arr){
        //定义两个点,都在左上角
        int a_row = 0;
        int a_col = 0;
        int b_row = 0;
        int b_col = 0;
        int N = arr.length - 1;
        int M = arr[0].length - 1;
        boolean flag = false;
        while (a_row != N + 1){
            printObliqueLine(arr,a_row,a_col,b_row,b_col,flag);
            //a 点向右移动,当到达末端时,向下移动
            a_row = a_col == M ? a_row + 1 : a_row;  //a点先判断行,在判断列 (列的变化会导致行变化)
            a_col = a_col == M ? a_col : a_col + 1;
            //b 点向下移动,当到达底端时,向右移动
            b_col = b_row == N ? b_col + 1 : b_col;  //b点先判断列,在判断行 (行的变化会导致列变化)
            b_row = b_row == N ? b_row : b_row + 1;
            flag = !flag;
        }
        System.out.println();
    }

    public void printObliqueLine(int[][] arr,int a_row,int a_col,int b_row,int b_col,boolean flag){
        if(flag){
            while (a_row != b_row + 1){  //上向下打印
                System.out.print(arr[a_row++][a_col--] + "\t");
            }
        }else {
            while (b_col != a_col + 1){  //下向上打印
                System.out.print(arr[b_row--][b_col++] + "\t");
            }
        }
    }
}

用螺旋的方式打印矩阵,比如如下的矩阵:

0 1 2 3

4 5 6 7

8 9 10 11

打印顺序为:0 1 2 3 7 11 10 9  8 4 5 6

package class03;

import org.junit.Test;

/**
 * 螺旋打印
 */
public class Code03_SpiralPrint {

    //定义左上角和右下角形成一个框,来打印框的边界,再缩小框,继续打印即可
    public void spiralPrint(int[][] arr){
        if(arr == null || arr.length == 0){
            System.out.println();
        }
        int leftRow = 0;  //左上角
        int leftCol = 0;
        int rightRow = arr.length - 1;  //右下角
        int rightCol = arr[arr.length - 1].length - 1;
        while (leftCol < rightCol && leftRow < rightRow){  //打印标准情况下的数字(左上角和右上角不同行也不同列)
            for (int i = leftCol;i <= rightCol;i++){
                System.out.print(arr[leftRow][i] + "\t");
            }
            for (int i = leftRow + 1;i <= rightRow;i++){
                System.out.print(arr[i][rightCol] + "\t");
            }
            for (int i = rightCol - 1;i >= leftCol;i--){
                System.out.print(arr[rightRow][i] + "\t");
            }
            for (int i = rightRow - 1;i >= leftRow + 1;i--){
                System.out.print(arr[i][leftCol] + "\t");
            }
            leftCol++;   //左上角下移
            leftRow++;
            rightCol--;   //右下角上移
            rightRow--;
        }
        //剩余同行或者同列的没有打印
        if (leftRow == rightRow){
            for (int i = leftCol;i <= rightCol;i++){
                System.out.print(arr[leftRow][i] + "\t");
            }
        }else if(leftCol == rightCol){
            for (int i = leftRow;i <= rightRow;i++){
                System.out.print(arr[i][rightCol] + "\t");
            }
        }
    }
}

给定一个正方形矩阵,只用有限几个变量,实现矩阵中每个位置的顺时针转动90度,比如如下这个矩阵:

0 1 2 3

4 5 6 7

8 9 10 11

12 13 14 15

矩阵应该被调整为:

12 8 4 0

13 9 5 1

14 10 6 2

15 11 7 3

package class03;

import org.junit.Test;

/**
 * 正方形矩阵翻转90度
 */
public class Code04_SquareFlipped90 {

    //根据螺旋打印,进行每次的边界翻转,每次四条边上的数据一起变
    //  arr为正方形数组
    public int[][] squareFlipped90(int[][] arr){
        int N = arr.length;
        int M = arr[0].length;
        //定义左上角和右下角  由于正方形,左上角和右下角的行列相等
        int left = 0;
        int right = N - 1 - left;
        int temp = 0;
        while (left < right){  //对边框数据进行变换 因为是正方形,left=right时只有一个值,省去
            for (int i = left,j = right;i < right;i++,j--){
                temp = arr[left][i];
                arr[left][i] = arr[j][left];  //左边框 -> 上边框
                arr[j][left] = arr[right][j]; // 下边框 -> 左边框
                arr[right][j] = arr[i][right]; // 下边框 -> 右边框
                arr[i][right] = temp;  //右边框 -> 上边框
            }
            left++;
            right--;
        }
        return arr;
    }
}

题目十五:最小操作数(神仙题)

假设s和m初始化,s=”a”;m=s;

再定义两种操作,第一种操作:

m=s;

s=s+s;

第二种操作:

s=s+m;

求最小的操作步骤数,可以将s拼接到长度等于n

package class03;

/**
 * 假设s和m初始化,s=”a”;m=s;
 * 再定义两种操作,第一种操作:
 * m=s;
 * s=s+s;
 * 第二种操作:
 * s=s+m;
 * 求最小的操作步骤数,可以将s拼接到长度等于n
 */
public class Code05_MinOperation {
    
    //判断n是不是质数  n >= 2
    public boolean isPrim(int n){
        for(int i = 2;i < n;i++){  //质数除了自己和1的乘积,其余没有
            while (n % i == 0){
                return false;
            }
        }
        return true;
    }
    
    public int[] divsSumAndCount(int n){
        int sum = 0;
        int count = 0;
        for(int i = 2;i <= n;i++){  //求出n分解为质数的乘积,质数之和,以及质数的个数
            while (n % i == 0){
                sum += i;
                count++;
                n /= i;
            }
        }
        return new int[]{sum,count};
    }

    //如果n时质数,调用操作二就是最小的操作步骤
    //n = a * b * c * d  合数分解为质数的乘积
    public int minOps(int n){
        if(n < 2){
            return 0;
        }
        if(isPrim(n)){
            return n - 1;
        }
        int[] divsSumAndCount = divsSumAndCount(n);
        return divsSumAndCount[0] - divsSumAndCount[1];
    }
}

题目十六:字符串出现次数最多的前K个(大根堆、小根堆)

给定一个字符串类型的数组arr,求其中出现次数最多的前k个

package class03;

import java.util.*;

/**
 * 给定一个字符串类型的数组arr,求其中出现次数最多的前k个
 */
public class Code06_OccurrenceNumberPre_K {

    //通过建立词频表,放在大根堆,根据出现次数排列的大根堆
    public String[] pre_KOfMaxTimes1(String[] arr,int K){
        HashMap<String,Integer> hashMap = new HashMap<>();
        for(String s : arr){
            if(!hashMap.containsKey(s)){
                hashMap.put(s,1);
            }else {
                hashMap.put(s,hashMap.get(s) + 1);
            }
        }
        //根据词出现的个数建立对应的大根堆
        PriorityQueue<Map.Entry<String,Integer>> priorityQueue
                = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                return o2.getValue() - o1.getValue();
            }
        });
        //存入大根堆
        for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
            priorityQueue.add(entry);
        }
        //取出前K个
        String[] str = new String[K];
        for(int i = 0;i < K;i++){
            String key = priorityQueue.poll().getKey();
            str[i] = key;
        }
        return str;
    }

    //也可以建立只具有K个容量的小根堆,这样存放词频表,能进入门槛的放入(多了就开始删门槛放入)
    public String[] pre_KOfMaxTimes2(String[] arr,int K){
        HashMap<String,Integer> hashMap = new HashMap<>();
        for(String s : arr){
            if(!hashMap.containsKey(s)){
                hashMap.put(s,1);
            }else {
                hashMap.put(s,hashMap.get(s) + 1);
            }
        }
        //建立小根堆
        PriorityQueue<Map.Entry<String,Integer>> priorityQueue
                = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                return o1.getValue() - o2.getValue();
            }
        });
        for (Map.Entry<String, Integer> entry : hashMap.entrySet()){
            if(priorityQueue.size() <= 7){
                priorityQueue.add(entry);
            }else if (priorityQueue.size() > K
                    && priorityQueue.peek().getValue() < entry.getValue()){ //小根堆顶部词数小于后序添加的
                priorityQueue.poll();
                priorityQueue.add(entry);
            }
        }
        //取出前K个
        String[] str = new String[K];
        for(int i = 0;i < K;i++){
            String key = priorityQueue.poll().getKey();
            str[i] = key;
        }
        return str;
    }
}

实时显示前K个,需要自己创建堆结构

package class03;

import java.util.HashMap;

/**
 * 实时的显示最多K系统
 */
public class Code07_TopKTimes {

    //存放字符串和字符串出现次数的节点 Node
    class Node{
        public String str;
        public int times;

        public Node(String str,int times){
            this.str = str;
            this.times = times;
        }
    }
    
    class TopKRecord{
        private  Node[] heap;  //堆
        private int heapSize; //堆的大小
        private HashMap<String,Node> strNodeMap;    // 字符串表  相当于词频表
        private HashMap<Node,Integer> nodeIndexMap; // 节点位置表

        public TopKRecord(int size){
            heap = new Node[size];
            heapSize = 0;
            strNodeMap = new HashMap<String,Node>();
            nodeIndexMap = new HashMap<Node,Integer>();
        }

        //添加字符串
        public void add(String str){
            Node curNode = null;  //当前str对应的节点对象
            int preIndex = -1;  //当前str节点对象是否在堆上
            if(!strNodeMap.containsKey(str)){//先看词频表里面有没有,没有词频表添加,且数为1;并且在节点的位置表中,初始化位置为-1
                curNode = new Node(str,1);
                strNodeMap.put(str,curNode);
                nodeIndexMap.put(curNode,-1);
            }else {  //词频表已经有,修改词频表对应字符串节点的次数,并获得该字符串节点之前在节点位置表的位置
                curNode = strNodeMap.get(str);
                curNode.times++;
                preIndex = nodeIndexMap.get(curNode);
            }
            //判断是否在堆上
            if(preIndex == -1){ //不在堆上,词频增加,是否要上堆
                if(heapSize == heap.length){ //堆满
                    if(heap[0].times < curNode.times){ //干掉堆顶,替换,并把位置表中对应节点位置改变
                        nodeIndexMap.put(heap[0],-1);
                        nodeIndexMap.put(curNode,0);
                        heap[0] = curNode;
                        heapify(0,heapSize);  //堆中以小根堆方式变化
                    }
                }else { //堆没有满,直接添加,堆的大小增加
                    nodeIndexMap.put(curNode,heapSize);
                    heap[heapSize] = curNode;
                    heapInsert(heapSize++);
                }
            }else {  //在堆上
                heapify(preIndex,heapSize);
            }
        }

        //把堆按照词频次数从小到大,节点位置表也需要对应修改
        public void heapify(int start,int end){
            for(int i = start;i < end;i++) {
                if (heap[i].times > heap[i+1].times){
                    Node temp = heap[i];
                    heap[i] = heap[i + 1]; //交换两个位置的节点
                    heap[i + 1] = temp;
                    nodeIndexMap.put(heap[i],i+1);  //对应节点的位置也需要改变
                    nodeIndexMap.put(heap[i+1],i);
                }
            }
        }

        //当前堆中已有节点的数加一
        public void heapInsert(int index){
            this.heapSize = heapSize;
        }
    }
}

题目十七:猫狗队列

实现一种猫狗队列的结构,要求如下:

用户可以调用add方法将cat类或dog类的实例放入队列中;

用户可以调用pollAll方法,将队列中所有的实例按照进队列的先后顺序依次弹出;

用户可以调用pollDog方法,将队列中dog类的实例按照进队列的先后顺序依次弹出;

用户可以调用pollCat方法,将队列中cat类的实例按照进队列的先后顺序依次弹出;

用户可以调用isEmpty方法,检查队列中是否还有dog或cat实例;

用户可以调用isDogEmpty方法,检查队列中是否有dog类的实例;

用户可以调用isCatEmpty方法,检查队列中是否有cat类的实例;

要求以上所有的方法时间复杂度都是O(1)

使用两个队列,记录他们进队列的先后顺序即可

题目十八:特殊栈

实现一个特殊的栈,在实现栈的基本功能上,再实现返回栈中最小元素的操作。

要求:1.pop、push、getMin操作的时间复杂度都是O(1),2.设计的栈类型可以使用现成的栈结构。

使用两个栈,一个存放数据,一个存放数据栈中的最小值

package class04;

import com.sun.java.swing.plaf.windows.WindowsTextAreaUI;

import java.util.Stack;

/**
 * 实现一个特殊的栈,在实现栈的基本功能上,再实现返回栈中最小元素的操作。
 * 要求:1.pop、push、getMin操作的时间复杂度都是O(1),2.设计的栈类型可以使用现成的栈结构。
 */
public class MinNumStack {

    Stack<Integer> numstack;  //数据栈
    Stack<Integer> minstack;  //最小值栈
    private int min;

    public MinNumStack(){
        numstack = new Stack<Integer>();
        min = Integer.MAX_VALUE;
    }

    public Integer poll() throws Exception{
        if (!numstack.isEmpty()){
            minstack.pop();
            return numstack.pop();
        }else {
            throw new RuntimeException("空栈");
        }
    }

    public void push(Integer num){
        min = Math.min(min,num);
        numstack.push(num);
        minstack.push(min);
    }

    public Integer getMin() throws Exception{
        if(!minstack.isEmpty()){
            return minstack.peek();
        }else {
            throw new RuntimeException("栈空,没有最小值");
        }
    }
}

题目十九:队列实现栈,栈实现队列

如何仅用队列结构实现栈结构?  两个队列

如何仅用栈结构实现队列结构?  两个栈

题目二十:动态规划的空间压缩技巧

给你一个二维数组matrix,其中每个数都是正数,要求从左上角走到右下角。每一步只能向右或者向下,沿途经过的数字要累加起来。最后请返回最小的路径和。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值