剑指 Offer 56 - I. 数组中数字出现的次数(找到数组中两个只出现一次的数字)找到小镇的法官、课程表(拓扑排序)、乘积最大连续子数组、从前序与中序遍历序列构造二叉树(元素唯一)

目录

剑指 Offer 56 - I. 数组中数字出现的次数(找到数组中两个只出现一次的数字)

异或的运算法则:

一、ret最终答案就是那两个只出现一次的的数异或的结果

二、找到ret二进制数中第几位是1

三、我们假设就以第一个二进制位为划分标准。(其实target其他位如果有1也可以作为分界线,但是为了防止万一只有一位不同还是选择第一个为1的位)

位运算方法 完整代码:

LeetCode 997.找到小镇的法官 (有向图出度为0&&入度为n-1)

一、将每个居民当作一个节点,记录节点出度和入度

二、for i=1遍历居民,找到同时满足两个条件的节点

完整代码:

LeetCode 207课程表(判断有向图是否有环) 

一、定义List> list = new ArrayList<>();和visit数组,并初始化

二、存每个节点的出度info[0]

三、dfs深搜未visit=0的科目判断是否有环(拓扑排序)

拓扑排序dfs完整代码:

LeetCode 152乘积最大子数组 

LeetCode 105从前序与中序遍历序列构造二叉树(元素唯一)

 一、构造哈希映射帮助我们快速定位根节点

二、递归:从前序遍历找根节点、从中序遍历定位到root下标和length



剑指 Offer 56 - I. 数组中数字出现的次数(找到数组中两个只出现一次的数字)

异或的运算法则:

1. a ⊕ a = 0

2. a ⊕ 0 = a

3. a ⊕ b = b ⊕ a

4. a ⊕b ⊕ c = a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c;

5. d = a ⊕ b ⊕ c 可以推出 a = d ⊕ b ⊕ c.

6. a ⊕ b ⊕ a = b.

7.若x是二进制数0101,y是二进制数1011 则x⊕y=1110 只有在两个比较的位不同时其结果是1,否则结果为0 即“两个输入相同时为0,不同则为1”! 输入 运算符 输入 结果 1 ⊕ 0 1 1 ⊕ 1 0 0 ⊕ 0 0 0 ⊕ 1 1

一、ret最终答案就是那两个只出现一次的的数异或的结果

得到a,b异或后的结果 = 得到nums数组异或后的结果

二、找到ret二进制数中第几位是1

找到第一个不同的位--->第一个!=0;==0就左移1位   
// 也可以写成!=1,一个意思

三、我们假设就以第一个二进制位为划分标准。(其实target其他位如果有1也可以作为分界线,但是为了防止万一只有一位不同还是选择第一个为1的位)

当数组中的数的第一个二进制位是1的就分为第一组。数组中的数第一个二进制位是0的就划分为第二组。这样就成功的将1和6分到了不同的组别中,而相同的数例如4,因为4和4的第一个二进制位是必然相等的,这样也就实现了将两个相同的数划分到同一组。最后只需要分别将这两个组进行异或,就可以得到我们要求的答案。

利用与target异或为0/1来将a,b到不同组

位运算方法 完整代码:

public static int[] singleTowNumber(int[] nums) {
        int ret = 0;
        for (int n : nums) {   // 得到a,b异或后的结果
            ret ^= n;
        }
        int target = 1;
        while ((target & ret) == 0) {   // 找到第一个不同的位--->第一个!=0;==0就左移1位   // 也可以写成!=1,一个意思
            target <<= 1;
        }
        int a = 0, b = 0;
        for (int n : nums) {   // 利用与target异或为0/1来将a,b到不同组
            if ((n & target) == 0) {
                a ^= n;
            } else {
                b ^= n;
            }
        }
        return new int[]{a, b};
    }

    public static void main(String[] args) {
//        int[] ans = singleNumbers(new int[]{1,2,10,4,1,4,3,3});
        int[] ans = singleTowNumber(new int[]{1,2,10,4,1,4,3,3});
        for (int n : ans) {
            System.out.print(n + " ");
        }
    }

LeetCode 997.找到小镇的法官 (有向图出度为0&&入度为n-1)

 997. 找到小镇的法官

建立有向图。将小镇里的每个人当成一个节点,那么对于法官这个节点来说,指向它的边的数量减去它指向的边的数量一定是N-1,可以理解为七桥问题。(边表示为it[0]指向it[1]) 

一、将每个居民当作一个节点,记录节点出度和入度

使用for each遍历二维数组,记录居民i的出度、入度

二、for i=1遍历居民,找到同时满足两个条件的节点

一次for循环遍历居民,找到同时符合1、2条件的居民i

完整代码:

public static int findJudge(int n, int[][] trust) {
        int[] inDegrees = new int[n + 1];   // 存i的入度
        int[] outDegrees = new int[n + 1];   // 存i的出度
        for (int[] info : trust) {   // 使用for each遍历二维数组,记录居民i的出度、入度
            outDegrees[info[0]]++;
            inDegrees[info[1]]++;
        }
        for (int i = 1; i <= n; i++) {   // 一次遍历居民,找到同时符合1、2条件的居民i
            if (inDegrees[i] == n - 1 && outDegrees[i] == 0) {
                return i;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        int n = 3;
        int[][] trust = new int[][]{{1, 3}, {2, 3}};
//        int ans = findJudge(n, trust);
        int ans = find(n, trust);
        System.out.println(ans);
    }

LeetCode 207课程表(判断有向图是否有环) 

207. 课程表

拓扑排序:

在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。

该序列必须满足下面两个条件:

  1. 每个顶点出现且只出现一次。
  2. 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。

例如,下面这个有向图:

它是一个 DAG 图,那么如何写出它的拓扑排序呢?这里说一种比较常用的方法:

  1. 从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。从图中删除该顶点和所有以它为起点的有向边。
  2. 重复 1 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。

于是,得到拓扑排序后的结果是 { 1, 2, 4, 3, 5 }。

通常,一个有向无环图可以有一个或多个拓扑排序序列。

拓扑排序的应用

拓扑排序通常用来“排序”具有依赖关系的任务。

比如,如果用一个DAG图来表示一个工程,其中每个顶点表示工程中的一个任务,用有向边A-->B表示在做任务 B 之前必须先完成任务 A。故在这个工程中,任意两个任务要么具有确定的先后关系,要么是没有关系,绝对不存在互相矛盾的关系(即环路)。

一、定义List<List<Integer>> list = new ArrayList<>();和visit数组,并初始化

初始化,不初始化size()为0,可能是null这种

二、存每个节点的出度info[0],要学0,先学1,所以是1指向0:

list.get(info[1]).add(info[0]);
// 学info[0]之前要先学info[1],所以info[1]指向info[0],
// 所以在info[1]的ArrayList中存储它指向哪个科目

三、dfs深搜未visit=0的科目判断是否有环(拓扑排序),排序完栈的作用是从栈顶往下存放最终的拓扑排序结果

if (visit[i]==0){ // 如果是未搜索的科目,则深搜
    dfs(i);
}

拓扑排序dfs完整代码:

public class 第207课程表 {

    List<List<Integer>> list=new ArrayList<>();
    int visit[];
    boolean isvaild=true;
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        visit = new int[numCourses];
        for (int i=0;i<numCourses;i++){
            list.add(new ArrayList<Integer>());// 初始化,不初始化size()为0,可能是null这种
        }
        for (int[] info:prerequisites){   // 存每个节点的出度info[0]
            // 学info[0]之前要先学info[1],所以info[1]指向info[0],
            // 所以在info[1]的ArrayList中存储它指向哪个科目
            list.get(info[1]).add(info[0]);
        }
        for (int i=0;i<numCourses;i++){
            if (visit[i]==0){ // 如果是未搜索的科目,则深搜
                dfs(i);
            }
        }
        return isvaild;
    }
    public void dfs(int v){
        visit[v]=1; // 标记该科目为搜索中
        for (int w:list.get(v)){ // 遍历该科目指向的学科,两种情况都不满足就执行 visit[v]=2;了
            if (visit[w]==0){ // 如果指向的某一学科未搜索过,则深搜
                dfs(w);
                if (!isvaild){
                    return;
                }
            } else if (visit[w]==1){ // 如果指向的某一学科在搜索中,则有环,标记isVaild
                isvaild=false;
                return;
            }
        }
        visit[v]=2; // 因为该科目已经完成深搜,所以标记它的状态为搜索过
    }

    public static void main(String[] args) {
        第207课程表 main = new 第207课程表();
        boolean ans = main.canFinish(2, new int[][]{{1, 0}});
        System.out.println(ans);
    }
}

LeetCode 152乘积最大子数组 

152. 乘积最大子数组

由于存在负数,那么会导致最大的变最小的,最小的变最大的。因此还需要维护当前最小值imin。

一个用来存最大值,一个用来存当前最小值。

leetcode上的图解:

输入为[2, 3, -2, 4],循环到-2的时候,(正*负,负)取最大imax变为-2;下一步imax又变为4;

之后多加-2交换,(正*负, 负)取最小imin会存-8:

当下一次再出现负数的时候,会重复上面过程

完整代码:

public static int maxProduct(int[] nums) {
    int max = Integer.MIN_VALUE, imax = 1, imin = 1;
    for(int i=0; i<nums.length; i++){   // 出现负号就调换
        if(nums[i] < 0){
            int tmp = imax;
            imax = imin;
            imin = tmp;
        }
        imax = Math.max(imax*nums[i], nums[i]);   // 每次存最大值和最小值(疑似最大值)
        imin = Math.min(imin*nums[i], nums[i]);   // 对于最小值来说,最小值是本身则说明这个元素值比前面连续子数组的最小值还小。相当于重置了阶段最小值的起始位置

        max = Math.max(max, imax);
    }
    return max;
}
public static void main(String[] args) {
    int[] str = new int[] {2, 3, -2, 4};
    System.out.println(maxProduct((str)));
}

LeetCode 105从前序与中序遍历序列构造二叉树(元素唯一)

105. 从前序与中序遍历序列构造二叉树

 一、构造哈希映射帮助我们快速定位根节点

public TreeNode buildTree(int[] preorder, int[] inorder) {   // 构造哈希映射,帮助我们快速定位根节点
    for (int i = 0; i < inorder.length; i++) {
        map.put(inorder[i], i);
    }

二、递归:从前序遍历找根节点、从中序遍历定位到root下标和length

对于任意一颗树而言,前序遍历的形式总是
[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]
即根节点总是前序遍历中的第一个节点。而中序遍历的形式总是
[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] 

完整代码:

public class 第105题从前序与中序遍历序列构造二叉树 {
    Map<Integer, Integer> map = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {   // 构造哈希映射,帮助我们快速定位根节点
        for (int i = 0; i < inorder.length; i++) {
            map.put(inorder[i], i);
        }
        int n = inorder.length;
        return buildTree(preorder, inorder, 0, n - 1, 0, n -1);
    }

    private TreeNode buildTree(int[] preorder, int[] inorder, int pre_start, int pre_end, int in_start, int in_end) {
        if (pre_start > pre_end) return null;

        // 只用到了root,root_index
        TreeNode root = new TreeNode(preorder[pre_start]);   // 构建根节点root
        int root_index = map.get(preorder[pre_start]);   // 从哈希映射中找到root_index
        int left_number = root_index - in_start;   // 找到本次迭代中左子树的长度
        // 递归找到root.left,当左子树数量=0的时候,pre_start + 1 > pre_start + left_number,找到了return条件
        root.left = buildTree(preorder, inorder, pre_start + 1, pre_start + 1 + left_number - 1, in_start, root_index - 1);
        root.right = buildTree(preorder, inorder, pre_start + left_number + 1, pre_end, root_index + 1, in_end);
        return root;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值