目录
剑指 Offer 56 - I. 数组中数字出现的次数(找到数组中两个只出现一次的数字)
三、我们假设就以第一个二进制位为划分标准。(其实target其他位如果有1也可以作为分界线,但是为了防止万一只有一位不同还是选择第一个为1的位)
LeetCode 997.找到小镇的法官 (有向图出度为0&&入度为n-1)
一、定义List> list = new ArrayList<>();和visit数组,并初始化
三、dfs深搜未visit=0的科目判断是否有环(拓扑排序)
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)
建立有向图。将小镇里的每个人当成一个节点,那么对于法官这个节点来说,指向它的边的数量减去它指向的边的数量一定是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课程表(判断有向图是否有环)
拓扑排序:
在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。
该序列必须满足下面两个条件:
- 每个顶点出现且只出现一次。
- 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。
例如,下面这个有向图:
它是一个 DAG 图,那么如何写出它的拓扑排序呢?这里说一种比较常用的方法:
- 从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。从图中删除该顶点和所有以它为起点的有向边。
- 重复 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乘积最大子数组
由于存在负数,那么会导致最大的变最小的,最小的变最大的。因此还需要维护当前最小值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从前序与中序遍历序列构造二叉树(元素唯一)
一、构造哈希映射帮助我们快速定位根节点
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;
}
}