bj_刷题技巧总结

1.常用输入

	   Scanner sc = new Scanner(System.in);
        
        //单行输入
//        int n=sc.nextInt();//键盘实际输入是“数字+回车”,nextInt读出了“数字”,并留下了“回车”,
//                            // 接着nextLine读到了一个“回车”,这是字符串的结束判定符,所以读到的字符串是空字符串
//        sc.nextLine();//去除回车
//        String s = sc.nextLine();

        //多行输入
//        while (sc.hasNext()){
//            String s1 = sc.nextLine();
//        }

//        //多行输入的数组接收(已知行数、列数)
//        int n=sc.nextInt();
//        sc.nextLine();
//        int[][] a=new int[n][3];  //已知列数为3
//        for (int i=0;i<n;i++){
//            String s0 = sc.nextLine();
//            String s = s0.substring(1, s0.length() - 1);  //带符号输入字符串的切割:先取子串
//            String[] c=s.split(" ");
//
//            for (int j=0;j<c.length;j++){
//                a[i][j]=Integer.parseInt(c[j]);
//            }
//        }
//        //常见自定义输出
//        for (int i=0;i<n;i++){
//            System.out.print("{");
//                System.out.print(a[i][j]+" ");
//            }
//            System.out.println(a[i][2]+"}");
//        }

        //多行输入的数组接收(未知行数、已知列数,但有结束提示)
        ArrayList<String> arr = new ArrayList<>();

        while (sc.hasNext()){
            String s = sc.nextLine();
            String[] s1 = s.split(" ");
            if (Integer.parseInt(s1[0])==0&&Integer.parseInt(s1[1])==0){   //if(结束条件)
                break;
            }
            arr.add(s);

        }
        int[][] a = new int[arr.size()][2];
        for (int i=0;i<arr.size();i++){
            String s = arr.get(i);
            String[] s1 = s.split(" ");

            for (int j = 0; j<s1.length;j++) {
                a[i][j] = Integer.parseInt(s1[j]);
                System.out.println(a[i][j]);
            }
        }

2.HashMap两种排序方法

        Map phone=new HashMap();
        phone.put("Apple",7299);
        phone.put("SAMSUNG",6000);
        phone.put("Meizu",2698);
        phone.put("Xiaomi",2400);

// 1.按key排序(从小到大)
对名称进行排序,首先要得到HashMap中键的集合(keySet),并转换为数组,这样才能用Arrays.sort()进行排序
        Set set=phone.keySet();
        Object[] arr=set.toArray();
        Arrays.sort(arr);
        for(Object key:arr){
                System.out.println(key+": "+phone.get(key));
        }
// 2.按value排序(从小到大、从大到小)
对value进行排序,首先需要得到HashMap中的包含映射关系的视图(entrySet),
如图:
	[Meizu=2698;Xiaomi=2400;SAMSUNG=6000;Apple=7299]
// 将entrySet转换为List,然后重写比较器比较即可.这里可以使用List.sort(comparator),也可以使用Collections.sort(list,comparator)转换为list

// 2.1 HashMap转List进行排序
        List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(phone.entrySet()); //转换为list

      //使用list.sort()排序
      list.sort(new Comparator<Map.Entry<String, Integer>>() {
          @Override
          public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
              return o2.getValue().compareTo(o1.getValue());   //从大到小
              // return o1.getValue().compareTo(o2.getValue());   //从小到大
          }
      });
	//使用Collections.sort()排序
     Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
           @Override
           public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
               return o2.getValue().compareTo(o1.getValue());    //从大到小
               // return o1.getValue().compareTo(o2.getValue());   //从小到大
           }
       });

        //for循环输出
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).getKey() + " " + list.get(i).getValue());
        }

student(id,score)按value排序

//输入学生人数,ID 和成绩,按成绩从大到小进行排序,成绩相同则按 ID 从小到大排序
//例如:
//输入
    3
    1 98
    2 94
    3 100
//输出
    3 100
    1 98
    2 94

import java.util.*;

public class yuanjingAI {
    public static void main(String[] args) {
        HashMap hm = new HashMap();
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();  //键盘实际输入是“数字+回车”,nextInt读出了“数字”,并留下了“回车”,
                               // 接着netxLine读到了一个“回车”,这是字符串的结束判定符啊,所以读到的字符串								   就是空字符串“”。
        sc.nextLine();         //使用sc.nextLine()吃掉多余的回车

//        多行输入
        for (int i = 0;i < n;i++){
            String str = sc.nextLine();
            String str1[] = str.split(" ");
            hm.put(str1[0],str1[1]);
        }
//        System.out.println(hm.values());

        //HashMap转List进行排序
        List<Map.Entry<String, String>> list = new ArrayList<Map.Entry<String, String>>(hm.entrySet()); //转换为list

        //使用list.sort()排序
        list.sort(new Comparator<Map.Entry<String, String>>() {
            @Override
            public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
                return o2.getValue().compareTo(o1.getValue());
            }
        });

        //for循环输出
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).getKey() + " " + list.get(i).getValue());
        }

    }
}

标题十万个数据,找出重复次数最多的十个数据并打印

public class NumberRepetMost {
    public static void main(String[] args) {
        final int NUM = 100000;//十万个
        int t = 0;
        Random random = new Random(  );
        HashMap<Integer,Integer> hashMap = new HashMap<Integer, Integer>(NUM);//key:数据 value:出现次数
        //模拟十万个数据
    while (t < NUM){
        Integer i = random.nextInt(1000);
        if(hashMap.containsKey( i )){  //如果存在,出现次数+1,不存在出现次数为1
            hashMap.put( i,hashMap.get( i ) +1);
        }else{
            hashMap.put( i,1 );
        }
        t++;
    }

    //用来存储最终的十个结果,优先级队列  小根堆形式,最小的在堆顶.
    PriorityQueue<Map.Entry<Integer,Integer>> priorityQueue =
            new PriorityQueue<Map.Entry<Integer,Integer>>( 10, new Comparator<Map.Entry<Integer,Integer>>() {
        @Override
        public int compare(Map.Entry<Integer,Integer> o1, Map.Entry<Integer,Integer> o2) {
            return o1.getValue()-o2.getValue();
        }//o1-o2 可以这样理解,如果返回正数,说明o1-o2>0.返回负数说明o1-o2<0
    } );


    //通过hashmap的entrySet进行遍历.
    Iterator<Map.Entry<Integer,Integer>> iterator = hashMap.entrySet().iterator();
    //先随便放10个,priorityQueue会自动排序
    for (int i = 0; i < 10; i++) {
        priorityQueue.add( iterator.next());
    }

    //再放其他的
    while (iterator.hasNext()){
        Map.Entry<Integer,Integer> curEntry= iterator.next(); //当前遍历的.
        Map.Entry<Integer,Integer> topEntry = priorityQueue.peek(); //获得队列中最小的那一个.

        if(curEntry.getValue() > topEntry.getValue()){
            priorityQueue.remove( topEntry );
            priorityQueue.add( curEntry );
        }
    }

    //打印结果
    Iterator<Map.Entry<Integer,Integer>> iterator2 = priorityQueue.iterator();
    while (iterator2.hasNext()){
        Map.Entry<Integer,Integer> entry = iterator2.next();
        System.out.println(entry.getKey()+":出现了"+entry.getValue()+"次");
    }
}

3.根据二叉树前序和中序获得后续遍历

3.1重建二叉树

3.2获得后续遍历

3.3二叉树遍历口诀

前序遍历:根——左——右

中序遍历:左——根——右

后续遍历:左——右——根

链接:https://www.nowcoder.com/questionTerminal/8a19cbe657394eeaac2f6ea9b0f6fcf6?answerType=1&f=discussion
来源:牛客网

1. 分析
根据中序遍历和前序遍历可以确定二叉树,具体过程为:

	1.根据前序序列第一个结点确定根结点
	2.根据根结点在中序序列中的位置分割出左右两个子序列
	3.对左子树和右子树分别递归使用同样的方法继续分解
例如:
	前序序列{1,2,4,7,3,5,6,8} = pre
	中序序列{4,7,2,1,5,3,8,6} = in

    根据当前前序序列的第一个结点确定根结点,为 1
    找到 1 在中序遍历序列中的位置,为 in[3]
    切割左右子树,则 in[3] 前面的为左子树, in[3] 后面的为右子树
    则切割后的左子树前序序列为:{2,4,7},切割后的左子树中序序列为:{4,7,2};切割后的右子树前序序列为:{3,5,6,8},切割后的右子树中序序列为:{5,3,8,6}
    对子树分别使用同样的方法分解。
 
2.代码
import java.util.Arrays;

public class BinaryTree {
    public static void main(String[] args) {
        int[] pre = {1, 2, 3, 4, 5, 6, 7};
        int[] in = {2, 3, 1, 5, 6, 4, 7};
        postOrder(reConstructBinaryTree(pre,in));
    }
//二叉树定义
    public static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) { val = x; }
    }
//根据重建后的root获得后续遍历结果
    private static void postOrder(TreeNode localRoot) {
        if (localRoot != null) {
            postOrder(localRoot.left);
            postOrder(localRoot.right);
            System.out.print(localRoot.val + "   ");
        }
    }
//根据前序和中序遍历获得重建二叉树
    public static TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if (pre.length == 0 || in.length == 0) {
            return null;
        }
        TreeNode root = new TreeNode(pre[0]);
        // 在中序中找到前序的根
        for (int i = 0; i < in.length; i++) {
            if (in[i] == pre[0]) {
                // 左子树,注意 copyOfRange 函数,左闭右开
                root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(in, 0, i));
                // 右子树,注意 copyOfRange 函数,左闭右开
                root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(in, i + 1, in.length));
                break;
            }
        }
        return root;
    }
}

3.4二叉树的深度

// 实现二叉树的深度方式有两种,递归以及非递归。

// ①递归实现:(掌握)

为了求树的深度,可以先求其左子树的深度和右子树的深度,可以用递归实现,递归的出口就是节点为空。返回值为0// ②非递归实现:

利用层次遍历的算法,设置变量level记录当前节点所在的层数,设置变量last指向当前层的最后一个节点,当处理完当前层的最后一个节点,让level指向+1操作。设置变量cur记录当前层已经访问的节点的个数,当cur等于last时,表示该层访问结束。

层次遍历在求树的宽度、输出某一层节点,某一层节点个数,每一层节点个数都可以采取类似的算法。

树的宽度:在树的深度算法基础上,加一个记录访问过的层节点个数最多的变量max,在访问每层前max与last比较,如果max比较大,max不变,如果max小于last,把last赋值给max;

// 代码实现:
import java.util.LinkedList;
public class Deep{
    //递归实现1
  public int findDeep(BiTree root){
      int deep = 0;
      
        if(root != null)
              {
                  int lchilddeep = findDeep(root.left);
                  int rchilddeep = findDeep(root.right);
                  deep = lchilddeep > rchilddeep ? lchilddeep + 1 : rchilddeep + 1;
              }

        return deep;
  }
    //递归实现2
  public int findDeep1(BiTree root){
        if(root == null){
           return 0;
         }
        else{
            int lchilddeep = findDeep1(root.left);//求左子树的深度
            int rchilddeep = findDeep1(root.left);//求右子树的深度
       return lchilddeep > rchilddeep ? lchilddeep + 1 : rchilddeep + 1;//左子树和右子树深度较大的那个加一等于整个树的深度
      }
  }
    
  //非递归实现
  public int findDeep2(BiTree root){
     if(root == null)
         return 0;
    
     BiTree current = null;
     LinkedList<BiTree> queue = new LinkedList<BiTree>();
     queue.offer(root);
     int cur,last;
     int level = 0;
     while(!queue.isEmpty())
     {
         cur = 0;//记录本层已经遍历的节点个数
         last = queue.size();//当遍历完当前层以后,队列里元素全是下一层的元素,队列的长度是这一层的节点的个数
         while(cur < last)//当还没有遍历到本层最后一个节点时循环
         {
             current = queue.poll();//出队一个元素
             cur++;
             //把当前节点的左右节点入队(如果存在的话)
             if(current.left != null)
             {
                 queue.offer(current.left);
             }
             if(current.right != null)
             {
                 queue.offer(current.right);
             }
         }
         level++;//每遍历完一层level+1
     }
     return level;
  }
  public static void main(String[] args)
 {
    BiTree root = BiTree.buildTree();
    Deep deep = new Deep();
    System.out.println(deep.findDeep(root));
    System.out.println(deep.findDeep1(root));
    System.out.println(deep.findDeep2(root));  
 }
}

3.5二叉树任意两个节点之间路径的最大长度

int maxDist(Tree
root) { 
    //如果树是空的,则返回0 
    if(root == NULL) 
        return 0; 
    if(root->left != NULL) { 
        root->lm = maxDist(root->left) +1; 
    } 
    if(root->right != NULL) 
        root->rm = maxDist(root->right) +1; 
    //如果以该节点为根的子树中有最大的距离,那就更新最大距离 
    int sum = root->rm + root->lm; 
    if(sum > max) { 
        max = sum; 
    } 
  
    return root->rm > root->lm ?root->rm : root->lm; 
}  

4.用两个栈实现队列

链接:https://www.nowcoder.com/questionTerminal/54275ddae22f475981afa2244dd448c6?answerType=1&f=discussion
来源:牛客网

1. 分析
    队列的特性是:“先入先出”,栈的特性是:“先入后出”

    当我们向模拟的队列插入数 a,b,c 时,假设插入的是 stack1,此时的栈情况为:

    栈 stack1:{a,b,c}
    栈 stack2:{}
    当需要弹出一个数,根据队列的"先进先出"原则,a 先进入,则 a 应该先弹出。但是此时 a 在 stack1 的最下面,将 stack1 中全部元素逐个弹出压入 stack2,现在可以正确的从 stack2 中弹出 a,此时的栈情况为:

    栈 stack1:{}
    栈 stack2:{c,b}
    继续弹出一个数,b 比 c 先进入"队列",b 弹出,注意此时 b 在 stack2 的栈顶,可直接弹出,此时的栈情况为:

    栈 stack1:{}
    栈 stack2:{c}
    此时向模拟队列插入一个数 d,还是插入 stack1,此时的栈情况为:

    栈 stack1:{d}
    栈 stack2:{c}
    弹出一个数,c 比 d 先进入,c 弹出,注意此时 c 在 stack2 的栈顶,可直接弹出,此时的栈情况为:

    栈 stack1:{d}
    栈 stack2:{}
    根据上述栗子可得出结论:

    当插入时,直接插入 stack1
    当弹出时,当 stack2 不为空,弹出 stack2 栈顶元素,如果 stack2 为空,将 stack1 中的全部数逐个出栈入栈 stack2,再弹出 stack2 栈顶元素
	
import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
        if(stack2.size()<=0){
            while(stack1.size()!=0){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

5.青蛙跳台阶——矩形覆盖问题(递归/动态规划)

链接:https://www.nowcoder.com/questionTerminal/8c82a5b80378478f9484d87d1c5f12a4?answerType=1&f=discussion
来源:牛客网

// 1.一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)

// 2.我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?比如n=3时,2*3的矩形块有3种覆盖方法:



    本质上还是斐波那契数列,所以迭代也可以求

    当成 dp 问题来想的话:首先分析问题,它最终解是由前面的解累积起来的解,如何缩小问题的规模?

    首先可知,第一阶有只能一步,一种;,第二阶可以两次一步、一次两步两种

    若楼梯阶级 n = 32 步到 3:剩下的是第一步没跳,起始跳到第一步只有一种
    跳 1 步到 3:剩下的是第二步没跳,起始跳到第二步有两种
    通过分类讨论,问题规模就减少了:

    若楼梯阶级 n = n
    跳 2 步到 n:剩下的是第 n - 2 步没跳,起始跳到第 n - 2 步设它为 pre2 种
    跳 1 步到 n:剩下的是第 n - 1 步没跳,起始跳到第 n - 1 步设它为 pre1 种
    同时可以发现第 n 阶的解法,只要用到 n - 1 和 n - 2 阶是多少,其他的不用考虑,因此用两个变量临时存下来即可

    dp(i) = dp(i-2) + dp(i-1)
    
// 解法1:递归
        public class Solution {
            public int JumpFloor(int target) {
                if(target<=2) return target;
                return JumpFloor(target-1)+JumpFloor(target-2);
            }
        }

// 解法2:动态规划
        public class Solution {
            public int JumpFloor(int target) {
                if(target<=2) return target;
                int pre1=2;
                int pre2=1;
                for(int i =3;i<=target;i++){
                    int cur = pre1+pre2;
                    pre2=pre1;
                    pre1=cur;
                }
                return pre1;
            }
        }

变态青蛙跳

//一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

题目分析:
    解法一: 动态规划。斐波那契数列的变种。由于青蛙每次可以跳n级台阶,因此青蛙跳上n级台阶的跳法为f(n)=f(n-1)+f(n-2)+……f(1)+f(0)。经过分析,发现结果呈规律性,f(n)2的n次幂形式增长,f(0)=1f(1)=1f(2)=2…从而可以归纳得到计算表达式:f(n)=2^(n-1)public class Solution {
    public int JumpFloorII(int target) {
        return target==0?1:(int)Math.pow(2,target-1);
    }
}

6.二进制中1的个数

链接:https://www.nowcoder.com/questionTerminal/8ee967e43c2c4ec193b040ea7fbb10b8?answerType=1&f=discussion
来源:牛客网

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

(搬运评论区大佬的解释)
如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。

举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

JAVA解:	
public class Solution {
    public int NumberOf1(int n) {
        int count=0;
        while(n!=0){
            count++;
            n=n&(n-1);
        }
        return count;
    }
}

7.调整数组使奇数在偶数之前

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

public class Solution {
    public void reOrderArray(int [] array) {
        int p=0;
        int m=0;
        int[] array1=new int[array.length];
        for(int i=0;i<array.length;i++){
            if(array[i]%2!=0){
                p++;     //查出奇数个数
            }
        }

        for(int i=0;i<array.length;i++){
            if(array[i]%2!=0){
                array1[m]=array[i];
                m++;
            }
            if(array[i]%2==0){
                array1[p]=array[i];
                p++;
            }
        }
        
        for(int i=0;i<array.length;i++){
            array[i]=array1[i]; 
        }
    }
}

8.链表中倒数第K个结点

//输入一个链表,输出该链表中倒数第k个结点

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/

public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        ListNode fast=head;
        ListNode slow=head;
        int i=0;           // i 必须为全局变量
        for(;fast!=null;i++){
            if(i>=k){
                slow=slow.next;
            }
            fast=fast.next;
        }
        return i < k ? null:slow;
    }
}

从尾到头打印链表

//输入一个链表,按链表从尾到头的顺序返回一个ArrayList

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
import java.util.Collections;
public class Solution {
    ArrayList<Integer> list = new ArrayList();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if (listNode != null) {
            printListFromTailToHead(listNode.next);
            list.add(listNode.val);
        }
        return list;
    } 
}

反转链表,输出新表头

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        //初始化pre指针,用于记录当前结点的前一个结点地址
        ListNode pre = null;
        //初始化p指针,用于记录当前结点的下一个结点地址
        ListNode p = null;
        //head指向null时,循环终止。
        while(head != null){
            //先用p指针记录当前结点的下一个结点地址。
            p = head.next;
            //让被当前结点与链表断开并指向前一个结点pre。
            head.next = pre;
            //pre指针指向当前结点
            pre = head;
            //head指向p(保存着原链表中head的下一个结点地址)
            head = p;
        }
        return pre;//当循环结束时,pre所指的就是反转链表的头结点
    }
}

9.排序算法比较

[外链图片转存中…(img-GeZ431BF-1586338963636)]

发布了7 篇原创文章 · 获赞 0 · 访问量 61

猜你喜欢

转载自blog.csdn.net/weixin_43903730/article/details/105393095