劍指Offer-2
阿新 • • 發佈:2020-12-14
只能說受益匪淺
1
判定入棧,出棧序列是否匹配
// 思路:用輔助棧來模擬出入棧 import java.util.Stack; public class Solution { public boolean IsPopOrder(int [] pushA,int [] popA) { int cnt = 0; // 記錄出棧個數或下標 Stack<Integer> stack = new Stack<>(); // 輔助棧 for(int i = 0; i < pushA.length; i++){ stack.push(pushA[i]); // 模擬入棧 while(!stack.isEmpty() && stack.peek() == popA[cnt]){ // while迴圈模擬出棧 stack.pop(); cnt++; } } return stack.isEmpty(); // 判斷輔助棧是否為空 } }
2
從上往下層級遍歷二叉樹
// 思路:用一個 佇列 模擬層次 // 用LinkedList模擬佇列 // 棧是addFirst,removeFirst // 佇列addLast,removeFirst // 這裡的層次遍歷,不是一層層來的,是一層裡面分左右子樹分開來的 import java.util.ArrayList; import java.util.LinkedList; public class Solution { public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) { ArrayList<Integer> list = new ArrayList(); // 存取層次遍歷序列 LinkedList<TreeNode> queue = new LinkedList(); // 儲存節點模擬層次的 if(root == null) return list; queue.addLast(root); while(!queue.isEmpty()){ TreeNode temp = queue.removeFirst(); // 出隊 list.add(temp.val); if(temp.left != null){ queue.addLast(temp.left); } if(temp.right != null){ queue.addLast(temp.right); } } return list; } }
// 用ArrayList模擬佇列 public class Solution { public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) { ArrayList<Integer> list = new ArrayList(); ArrayList<TreeNode> queue = new ArrayList(); if(root == null) return list; queue.add(root); while(queue.size() != 0){ TreeNode temp = queue.remove(0); list.add(temp.val); if(temp.left != null){ queue.add(temp.left); } if(temp.right != null){ queue.add(temp.right); } } return list; } }
3
判斷是否後序遍歷
// 現在開始自己規定,凡是自己傳進去的陣列長度這些引數,都是實際長度,就是length-1這種
// 思路:後序中最後一個是根,去除最後一個可以分成兩段。前段小於根,後段大於根,以此類推遞迴
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence == null || sequence.length == 0) return false;
return search(sequence,0,sequence.length-1);
}
private boolean search(int[] arr,int left,int right){
if(left >= right) return true; // 遞迴出口,這裡最重要
int mid = left; // 從左遍歷找分界(對比根),小心越界
while(arr[mid] < arr[right] && mid < right){
mid++;
}
for(int i = mid; i < right; i++){ // 判斷右端是否符合
if(arr[i] < arr[right]){
return false;
}
}
// 左去界(遍歷的時候比根大了才停止的),右去根
return search(arr,left,mid-1) && search(arr,mid,right-1);
}
}
4
二叉樹和為某值的路徑
import java.util.ArrayList;
public class Solution {
// 一個儲存當前遍歷的路徑,一個儲存符合的全部路徑
ArrayList<ArrayList<Integer>> list = new ArrayList<>();
ArrayList<Integer> path = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
search(root,target);
return list;
}
// 遍歷用前序或者DFS
private void search(TreeNode root, int target){
if(root == null) return ;
target -= root.val;
path.add(root.val);
if(root.left == null && root.right == null && target == 0){
list.add(new ArrayList<Integer>(path));
}
search(root.left,target);
search(root.right,target);
// 回溯時不用加回target,因為是個副本
path.remove(path.size()-1);
}
}
5
複雜連結串列的複製
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
// 思路:
// 1. 複製每個節點(暫不處理隨機指向),將新複製的節點插入原節點後面:A->A1
// 2. 處理隨機指向
// 3. 複製連結串列和原連結串列分離
public class Solution {
public RandomListNode Clone(RandomListNode pHead){
if(pHead == null) return null;
// 1. 複製連結串列,複製節點插入到原節點後面
RandomListNode node = pHead;
while(node != null){
RandomListNode next = node.next;
RandomListNode cloneNode = new RandomListNode(node.label);
node.next = cloneNode; // 連結串列插入過程
cloneNode.next = next;
node = next; // 節點插入後,當前節點記得跳轉到next
}
// 2. 遍歷處理隨機指向
node = pHead;
while(node != null){
if(node.random != null){
// 重點:指向隨機的下一個(因複製時插入到後一個去了)
node.next.random = node.random.next;
}
node = node.next.next; // 複製插入要跳多一個
}
// 3. 分離節點,奇偶分離
RandomListNode oldNode = pHead;
RandomListNode newHead = pHead.next; // 新表頭
while(oldNode != null){
RandomListNode newNode = oldNode.next;
oldNode.next = newNode.next;
if(newNode.next != null){
newNode.next = newNode.next.next;
}
oldNode = oldNode.next; // 上面已經更新了舊節點指向,已經跳過一個節點了
}
return newHead;
}
}
// 3. 分離節點,奇偶分離
// RandomListNode oldNode = pHead;
// RandomListNode newNode = pHead.next; // 因為有複製,所以後一個節點一定不為空
// RandomListNode newHead = newNode; // 新表頭
// while(newNode.next != null){
// oldNode.next = newNode.next;
// oldNode = oldNode.next;
// newNode.next = oldNode.next;
// newNode = newNode.next;
// }
6
二叉搜尋樹轉變雙向連結串列
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
* 遞迴中序遍歷:左 根 右
* 下面if、else中的意思
* 4
/ \
* 3 5
* 第一步if:head與temp賦值3節點;
* 第二步else:改動temp節點互相指向,最後head賦值4節點:3 <--> 4
* 第三步else:改動temp節點互相指向,最後head賦值5節點:4 <--> 5
* 綜上:3 <--> 4 <--> 5,連結串列完成
*/
public class Solution {
TreeNode temp = null; // 臨時節點,幫助形成雙向連結串列
TreeNode head = null; // 表頭,用於返回
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null) return null; // 遞迴出口
Convert(pRootOfTree.left); // 左子樹遍歷
if (head == null) { // 首次要處理根節點
head = pRootOfTree; // 第一次訪問,記錄頭節點,用於訪問返回
temp = pRootOfTree;
} else {
temp.right = pRootOfTree; // 按中序遍歷順序連成連結串列,詳情看上面圖
pRootOfTree.left = temp; // 中序就是有序,只需將當前temp指向下一個即可
temp = temp.right; // 然後移動當前節點到下一個
}
Convert(pRootOfTree.right); // 右子樹遞迴
return head;
}
}
7
字串的排列(標準的DFS + 交換 / 回溯)
// 思路:根據字串的排列的特點,選擇深度優先搜尋,可通過字元交換實現,重複字元用剪枝
// 1. 分成兩部分,首個字元、後面的全部字元
// 2. 每個字元都可在首位,即首字元和後面的進行交換
// 3. 固定第一個字元,求後面的排列,即遞迴進行2,3步,出口為到了陣列長度
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Collections;
public class Solution {
// 儲存所有排列
ArrayList<String> res = new ArrayList();
public ArrayList<String> Permutation(String str) {
char[] arr = str.toCharArray(); // 轉成陣列容易遍歷
dfs(arr,0); // 實現排列
Collections.sort(res); // 排序
return (ArrayList) res;
}
private void dfs(char[] arr,int index){
if(index == arr.length - 1){ // 到葉子節點認為一個排列,遞迴出口
res.add(String.valueOf(arr));
return ;
}
HashSet<Character> set = new HashSet<>(); // 用來做剪枝的,防止重複
for(int i = index; i < arr.length; i++){ // 遍歷交換:使得每個元素都在首位
if(set.contains(arr[i])){
continue; // 重複,因此剪枝(假設每個元素不重複)
}
set.add(arr[i]);
swap(arr,index,i); // 1. 首位和後面的全部逐個交換,即每個元素都有在首位的可能
dfs(arr,index + 1); // 2. 固定首位,排列後面的字元
swap(arr,index,i); // 3. 回溯,不影響後面的每個元素都排在首位
}
}
// 交換
private void swap(char[] arr,int i,int j){
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
8
找出陣列中的一個出現的次數超過陣列長度的一半的數
// 思路1:遍歷多次,儲存每個元素出現的次數
// 思路2:排序後,眾數肯定出現在中間
// 最優解
// 思路:摩爾投票法,查詢超過1/2的數,肯定只有一個
// 流程:依次從序列中選擇兩個數字,若不同則抵消(票數-1),相同當前數值的票數+1,最後剩下的數字就是所找
// 步驟:1.對抗兩兩抵消、2.計算結果是否有效
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
int major = 0;
int count = 0; // 當前major的票數
for(int i = 0; i < array.length; i++){ // 從頭到尾遍歷
if(count == 0){
major = array[i];
}
if(major == array[i]){
count++;
}else{
count--;
}
}
int countRs = 0;
for(int num : array){
if(major == num){
countRs++;
}
}
return (countRs > array.length/2) ? major : 0;
}
}
9
找出其中最小的K個數,TopK問題(快排,堆排)
// 無腦解法
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
Arrays.sort(input);
ArrayList<Integer> list = new ArrayList<>();
for(int i = 0; i < k; i++){
list.add(input[i]);
}
return list;
}
}
// 快排,我叫為哨兵排
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
if(input == null || k > input.length) return list;
quickSort(input,0,input.length-1);
ArrayList<Integer> list = new ArrayList();
for(int i = 0; i < k; i++){
list.add(input[i]);
}
return list;
}
private void quickSort(int[] arr, int left,int right){
if(left > right) return ;
int base = arr[left];
int i = left,j = right;
while(i < j){ // 選基準交換
while(i < j && base <= arr[j]){
j--;
}
while(i < j && base >= arr[i]){
i++;
}
if(i < j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
arr[left] = arr[i]; // 基準歸位
arr[i] = base;
quickSort(arr,left,i-1); // 二分治
quickSort(arr,i+1,right);
}
}
10
計算連續子向量的最大和,有負數
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
if(array == null) return 0;
// 注意:題目指的連續,不一定從下標0開始,可以視窗滑動的
// maxSum不初始化為0,存在全負數情況,所以初始值array[0]
// maxSum儲存最大和
int curSum,maxSum;
curSum = maxSum = array[0];
for(int i = 1; i < array.length; i++){
// 一旦遇到和為負數,證明前面的正數效果作廢了
// 當前和小於0,拋棄前面的和,重新從現在加起
if(curSum < 0){
curSum = array[i];
}else if(curSum > 0){
curSum += array[i];
}
// 更新最大和
if(curSum > maxSum){
maxSum = curSum;
}
}
return maxSum;
}
}
11
計算整數中1出現的次數
// 暴力轉成字串判斷
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
StringBuffer str = new StringBuffer();
for(int i = 1;i <= n; i++){
str.append(i);
}
int count = 0;
String s = str.toString();
for(int i = 0;i < s.length(); i++){
if(s.charAt(i) == '1'){
count++;
}
}
return count;
}
}
// 區分位數:i表示當前位,其餘表示為高位和低位
// 每次迴圈取當前位,即高位模10(high % 10),分別有三種情況。如下:
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
int count = 0;
for(int i = 1; i <= n; i *= 10){ // 只需迴圈位數次
int high = n / i;
int low = n % i;
if(high % 10 == 0){
count += high / 10 * i;
}else if (high % 10 == 1){
count += (high / 10 * i) + (low + 1);
}else {
count += (high / 10 + 1) * i;
}
}
return count;
}
}
// 最優解,上面的優化
// 當百位 = 0,則high / 10 == (high + 8) / 10
// 當百位 > 1,取8就進位,效果等於(high / 10 + 1)
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
int count = 0;
for(int i = 1; i <= n; i *=10){
int high = n / i;
int low = n % i;
if(high % 10 == 1){
count += low + 1;
}
count += (high + 8) / 10 * i;
}
return count;
}
}
12
把陣列排成最小
// 本質是排序問題,一般用快排
public class Solution {
public String PrintMinNumber(int [] numbers) {
for(int i = 0; i < numbers.length-1; i++) // 氣泡排序
for(int j = 0; j < numbers.length-i-1; j++){
String str1 = numbers[j] + "" + numbers[j+1];
String str2 = numbers[j+1] + "" + numbers[j];
if(str1.compareTo(str2) > 0){ // 排到最後的是最大
int temp = numbers[j];
numbers[j] = numbers[j+1];
numbers[j+1] = temp;
}
}
String str = "";
for(int i = 0; i < numbers.length; i++){
str += numbers[i];
}
return str;
}
}
// 思路二
// 數字m、n拼接成 mn 和 nm
// 若mn>nm,則m大於n
// 若mn<nm,則m小於n
// 若mn=nm,則m等於n
13
醜數:把只包含質因子2、3和5的數
// 思路:一個醜數一定由另一個醜數乘以2或3或5得到
// 這就是動態規劃??
import java.util.ArrayList;
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index <= 0) return 0;
ArrayList<Integer> list = new ArrayList();
list.add(1); // 預設第一個醜數為1
// 用三個下標來模擬三個佇列的尾部,加入list證明已經排好序
int i2 = 0,i3 = 0,i5 = 0;
while(list.size() < index){ // 從各自的佇列取出
int m2 = list.get(i2)*2;
int m3 = list.get(i3)*3;
int m5 = list.get(i5)*5;
int min = Math.min(m2,Math.min(m3,m5));
list.add(min);
if(min == m2) i2++;
if(min == m3) i3++;
if(min == m5) i5++;
}
return list.get(list.size()-1);
}
}
14
第一個只出現一次的字元位置
// 雜湊表,VALUE存放次數
import java.util.HashMap;
public class Solution {
public int FirstNotRepeatingChar(String str) {
if(str.length() == 0) return -1;
HashMap<Character,Integer> map = new HashMap();
char arr = str.toCharArray();
for(int i = 0; i < str.length(); i++){
if( map.containsKey(str.charAt(i)) ){
int num = map.get(str.charAt(i));
map.put(str.charAt(i),num+1);
}else{
map.put(str.charAt(i),1);
}
}
for(int i = 0; i < str.length(); i++){
if( map.get(str.charAt(i)) == 1 ){
return i;
}
}
return 0;
}
}
// 變形體,返回第一個只出現一次的字元
// 雜湊表,VALUE存放次數,這裡一次的話可以存放TRUE/FALSE
import java.util.HashMap;
public class Solution {
public int FirstNotRepeatingChar(String str) {
char[] arr = str.toCharArray();
HashMap<Character,Boolean> hashMap = new HashMap<>();
for(char c : arr){
hashMap.put(c,!hashMap.containsKey(c));
}
for(char c : arr){
if(hashMap.get(c)){
return c;
}
}
return "";
}
}
15
陣列的逆序對
// 暴力破解法,雙層for迴圈,內層以i+1開頭(因為當前元素的前面才能構成逆序)
public class Solution {
public int reversePairs(int[] nums) {
int cnt = 0;
for (int i = 0; i < nums.length - 1; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] > nums[j]) {
cnt++;
}
}
}
return cnt;
}
}
// 最優解
// 歸併排序的利用,分治過程中前後數字可對比,是統計的最佳時機
public class Solution {
int count = 0; // 統計逆序對
public int InversePairs(int [] array) {
if(array == null || array.length == 0) return 0;
mergeSort(array,0,array.length-1);
return count;
}
private void mergeSort(int[] arr,int start,int end){
if(start < end){ // 拆分分治的過程,遞迴出口,長度為1預設排好序
int mid = start + (end - start) / 2;
mergeSort(arr,start,mid);
mergeSort(arr,mid+1,end);
merge(arr,start,mid,end); // 最後合併
}
}
private void merge(int[] arr,int start,int mid,int end){
int[] temp = new int[end - start + 1]; // 輔助陣列,最後賦值回原陣列
int i = start,j = mid + 1;
int index = 0;
while(i <= mid && j <= end){
if(arr[i] > arr[j]){
temp[index++] = arr[j++];
// 與歸併排序就多了下面這兩句
// 合併陣列時,array[i]大於後面array[j]時
// 則array[i]~array[mid]都是大於array[j]的,所以count += mid + 1 - i
count += mid - i + 1;
count = count > 1000000007 ? count % 1000000007 : count;
}else{
temp[index++] = arr[i++];
}
}
while(i <= mid)
temp[index++] = arr[i++];
while(j <= end)
temp[index++] = arr[j++];
for (int k = 0;k < temp.length;k++)
arr[start+k] = temp[k];
}
}
16
兩個連結串列的第一個公共結點
// 先走連結串列二者長度差,然後同步走到相同節點
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
// 0. 移動節點要記得復位,這裡卡了好久,不然NPE
ListNode temp1 = pHead1;
ListNode temp2 = pHead2;
// 1. 記錄二者的長度
int p1 = 0, p2 = 0;
while(pHead1 != null){
p1++;
pHead1 = pHead1.next;
}
while(pHead2 != null){
p2++;
pHead2 = pHead2.next;
}
if(pHead1 != pHead2) return null; // 尾節點都不相交,下面也無需遍歷了,簡化操作可忽略
// 2. 上面移動指標要復位
// 移動長連結串列,移動距離為二者長度差
pHead1 = temp1;
pHead2 = temp2;
if(p1 > p2){
int temp = p1 - p2;
while(temp > 0){
pHead1 = pHead1.next;
temp--;
}
}else{
int temp = p2 - p1;
while(temp > 0){
pHead2 = pHead2.next;
temp--;
}
}
// 3. 二者並行找相同節點
while(pHead1 != null || pHead2 != null){
if(pHead1 == pHead2){
return pHead1;
}
pHead1 = pHead1.next;
pHead2 = pHead2.next;
}
// 4. 沒有公共節點
return null;
}
}
// 思路二,兩條y狀的連結串列,從尾遍歷到頭,第一個不相同的就是交點,使用棧/遞迴實現
// 思路三:最優解,雙指標
// 兩個指標同步走,哪個到了連結串列尾,就設定為對方的頭節點繼續遍歷,最後會相遇
// 長度相同有公共結點,第一次就遍歷到;沒有公共結點,走到尾部NULL相遇,返回NULL
// 長度不同有公共結點,第一遍差值就出來了,第二遍一起到公共結點;沒有公共,一起到結尾NULL
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while(p1 != p2){
p1 = (p1 == null ? pHead2 : p1.next);
p2 = (p2 == null ? pHead1 : p2.next);
}
return p1;
}
}
17
統計一個數字在排序陣列中出現的次數(排序就二分)
// 思路:傻子做法
public class Solution {
public int GetNumberOfK(int [] array , int k) {
int cnt = 0;
for(int i = 0; i < array.length; i++){
if(k == array[i]){
cnt++;
}
}
return cnt;
}
}
// 思路:首先二分法,找到之後向前向後找
public class Solution {
public int GetNumberOfK(int [] array , int k) {
int cnt = 0;
int left = 0;
int right = array.length - 1;
int mid = -1;
while(left <= right){
mid = left + (right-left) / 2;
if(array[mid] == k){
cnt++;
break;
}else if(array[mid] < k){
left = mid + 1;
}else{
right = mid - 1;
}
}
if(mid == -1) return cnt; // 沒找到相同的,先退出了
for(int i = mid+1; i < array.length; i++){
if(array[i] == k) cnt++;
else break;
}
for(int i = mid-1; i >= 0; i--){
if(array[i] == k) cnt++;
else break;
}
return cnt;
}
}
// 思路三:最優,二分左右邊界,相減即可
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array == null || array.length == 0) return 0;
int first = getFirstK(array,k);
int last = getLastK(array,k);
if(first == -1 || last == -1) return 0;
else return last - first + 1;
}
private int getFirstK(int [] array, int k){
int low = 0;
int high = array.length - 1;
while(low <= high){
int mid = low + (high-low) / 2;
if(array[mid] == k){
high = mid - 1;
}else if(array[mid] > k){
high = mid - 1;
}else{
low = mid + 1;
}
}
if(low == array.length) return -1; // 這裡最重要
return array[low] == k ? low : -1;
}
private int getLastK(int [] array, int k){
int low = 0;
int high = array.length - 1;
while(low <= high){
int mid = low + (high - low) / 2;
if(array[mid] == k){
low = mid + 1;
}else if(array[mid] > k){
high = mid - 1;
}else{
low = mid + 1;
}
}
if(high == -1) return -1;
return array[high] == k ? high : -1;
}
}
18
求樹深
// 遞迴
public class Solution {
public int TreeDepth(TreeNode root) {
// 遞迴出口
if(root == null) return 0;
// 遞迴條件
return Math.max( TreeDepth(root.left)+1 , TreeDepth(root.right)+1 );
}
}
// 層次遍歷解決,此層次和之前的不同
// 每層次遍歷一遍,即深度+1
// 遍歷完就深度也出來了
// 注意,遍歷一次就要一層全部處理完,否則就不是樹深+1了
import java.util.LinkedList;
public class Solution {
public int TreeDepth(TreeNode root) {
int cnt = 0; // 層數
LinkedList<TreeNode> queue = new LinkedList(); // 儲存節點模擬層次的
if(root == null) return cnt;
queue.addLast(root);
while(!queue.isEmpty()){
int size = queue.size();
for(int i = 0; i < size; i++){ // for將當前層處理完
TreeNode temp = queue.removeFirst(); // 出隊
if(temp.left != null) queue.addLast(temp.left);
if(temp.right != null) queue.addLast(temp.right);
}
cnt++;
}
return cnt;
}
}
19
驗證平衡二叉樹平衡(任何結點的兩個子樹的高度差小於等於1),可以結合17題
// 遞迴
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
// 空也是一個平衡樹
if(root == null) return true;
return getDepth(root) != -1;
}
// 後序遍歷算深度,每個節點只用算一次
private int getDepth(TreeNode node){
if(node == null) return 0;
// 左樹的深度,+1動作放到最後,因為下面要判斷-1
int left = getDepth(node.left);
if(left == -1) return -1;
// 右樹的深度
int right = getDepth(node.right);
if(right == -1) return -1;
// 左右樹深度比較,也就是遞迴
if(Math.abs(left - right) > 1) return -1;
// 當前
return Math.max(left,right) + 1;
}
}
20
陣列中只出現一次的數字
// num1,num2分別為長度為1的陣列。傳出引數,將num1[0],num2[0]設定為返回結果即可,C語言題目垃圾
// 用HashSet去重特性
import java.util.HashSet;
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
HashSet<Integer> set = new HashSet();
// set.add,如果存在返回true,如為false則插入元素
for(int i = 0; i < array.length; i++){
if(set.contains(array[i])){
set.remove(array[i]);
}else{
set.add(array[i]);
}
}
Object[] temp = set.toArray();
num1[0] = (int) temp[0];
num2[0] = (int) temp[1];
}
}
// 最優解
// 使用陣列的異或運算,相同為0,不同為1,任何數與0異或為本身
// 如果一個數字只出現一次,其餘兩次,那麼全體異或過程中兩兩相同的就會抵消變為0,剩下的數和0異或得出本身
// eg:
int res = 0;
int[] nums = {1,1,2,3,4,5,3,4,5};
for(int value : nums){
res ^= value;
}
System.out.println(res); // 2
// ——————————————————————————————————————————————————————————————————————————————————————
// 如果出現了兩次,那麼就要進行分組異或
// 假如兩個不同的數為 a、b,那麼所有數字異或結果就是 a^b 的結果,記為x
// x轉成二進位制,其中的0和1表示a、b二進位制中相同和不同的部分
// 若選二進位制x中,不為0的位,按照該位分組,預設選不為0的最低位
// 流程:
// 1.對所有數字異或,得出x
// 2.在x中找不為0的最低位
// 3.根據這位對所有的數字進行分組
// 4.在每個組內進行異或操作,得到兩個數字
//num1,num2分別為長度為1的陣列。傳出引數
//將num1[0],num2[0]設定為返回結果
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int ret = 0;
for(int num : array){
ret ^= num;
}
int div = 1;
while((div & ret) == 0){
div <<= 1; // div兩倍關係,才能在二進位制上逐步變成1,所以分組也只能是2,4,6來分
}
int a = 0;
int b = 0;
for(int num : array){
if ((div & num) != 0) {
a ^= num;
} else {
b ^= num;
}
}
num1[0] = a;
num2[0] = b;
}
}