秋招数据结构与算法知识总结

异或运算(^)

性质

  1. 异或运算满足交换律和结合律
  2. 二进制异或运算相同为0,不同为1
  3. 二进制异或运算等同于无进位相加
  4. 0 ^ N = N, N ^ N = 0
  5. 数组中两个数交换可以使用异或运算。其中i和j不能相同,不然运算结果为0,但是nums[i]可以等于nums[j]
nums[i] = nums[i] ^ nums[j];
nums[j] = nums[i] ^ nums[j];
nums[i] = nums[i] ^ nums[j];
  1. 提取一个数中二进制最右边的1,做法:将这个数与他本身取反加一后的结果按位与
int rightOne = eor & (~eor + 1);

leetcode题目

只出现一次的数字
只出现一次的数字2
只出现一次的数字3

二分法

在一个有序数组中寻找某个数是否存在

  • 时间复杂度为O(logN),因为每次数组长度减半

在一个有序数组中找>=某个数最左侧的位置

局部最小

  • 无序数组中相邻数一定不相等

对数器(比对数字)

对数器
对数器2

master公式

T(N) = a * T(N / b) + O(N ^ d)
其中logba < d,则时间复杂度为O(N ^d)
logba > d,则时间复杂度为O(N ^ logba)
logba == d,则时间复杂度为O(N ^d * logN)

比较器

public class Comparator_test {
    public static void main(String[] args) {
        int[] arr = {1, 5, 2, 7, 4, 3, 9};
        Integer[] arr1  = {1, 5, 2, 7, 4, 3, 9};
//        System.arraycopy(arr, 0, arr1, 0, arr.length);
        Arrays.sort(arr);
        Arrays.sort(arr1, new arrayComparator());
        System.out.println("arr:" + Arrays.toString(arr)); // arr:[1, 2, 3, 4, 5, 7, 9]
        System.out.println("arr1:" + Arrays.toString(arr1)); // arr1:[9, 7, 5, 4, 3, 2, 1]
    }

    public static class arrayComparator implements Comparator<Integer> {
        // 如果返回负数,认为第一个参数应该在前面
        // 如果返回正数,认为第二个参数应该在前面
        // 如果返回0,认为谁在前面都行
        @Override
        public int compare(Integer a, Integer b) {
            return b - a;
        }
    }
}

排序算法

排序算法稳定性

基于比较的排序中,时间复杂度小于O(NlogN)的没有;时间复杂度为O(NlogN),空间复杂度小于O(N)且保持稳定性的算法没有

  • 相同的数保持原有次序
  • 选择排序稳定性差
  • 冒泡排序稳定性好,相邻元素相同不交换
  • 插入排序稳定性好
  • 归并排序稳定性好,merge时两个数值相同时,先拷贝右边即可
  • 快排稳定性差,partition过程中破坏稳定性
  • 堆排序稳定性差
  • 基数排序和技术排序稳定性好 (不基于比较的排序稳定性好
    常用排序算法稳定性总结

时间复杂度

  • 判断算法时间复杂度按照数据最差情况估计的时间复杂度,O表示最差情况

一、插入排序

  • 时间复杂度最差为O(N^2),最好为O(N)
class Solution {
    public int[] sortArray(int[] nums) {
        if (nums.length < 2) return nums;
        for (int i = 1; i < nums.length; ++i) {
            for (int j = i - 1; j >=0 && nums[j] > nums[j + 1]; --j) {
                swap(nums, j, j + 1);
            }
        }
        return nums;
    }

    public void swap(int[] nums, int i, int j) {
        nums[i] = nums[i] ^ nums[j];
        nums[j] = nums[i] ^ nums[j];
        nums[i] = nums[i] ^ nums[j];
    }
}
class Solution {
    public int[] sortArray(int[] nums) {
        if (nums.length < 2) return nums;
        for (int i = 0; i < nums.length - 1; ++i) {
            int sortedIndex = i;
            int currValue = nums[sortedIndex + 1];
            while (sortedIndex >= 0 && currValue < nums[sortedIndex]) {
                nums[sortedIndex + 1] = nums[sortedIndex];
                sortedIndex--; 
            }
            nums[sortedIndex + 1] = currValue;
        }
        return nums;
    }
}

二、归并排序

  • 时间复杂度O(NlogN),空间复杂度O(N)
public class solution {
    public static void main(String[] args) {
        int[] array = {10,199, 2, 32, 41, 15, 22, 75, 140};
        mergeSort(array);
        System.out.println(Arrays.toString(array));
    }

    public static void mergeSort(int[] arr) {
        int left = 0;
        int right = arr.length - 1;
        process(arr, left, right);
    }

    public static void process(int[] arr,  int left, int right) {
        if (left == right) return;
        int mid = left + ((right - left) >> 1 );
        process(arr, left, mid);
        process(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }

    public static void merge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int i = 0;
        int p1 = left;
        int p2 = mid + 1;
        while (p1 <= mid && p2 <= right) {
            temp[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= mid) {
            temp[i++] = arr[p1++];
        }
        while (p2 <= right) {
            temp[i++] = arr[p2++];
        }
        for (int j = 0; j < temp.length; j++) {
            arr[left + j] = temp[j];
        }
    }
}

leetcode题目

计算右侧小于当前元素的个数

题目

  1. 求最大值
public class solution {
    public static void main(String[] args) {
        int[] array = {10,199, 2, 32, 41, 15, 22, 75, 140};
        int ans = getMax(array);
        System.out.println(ans);
    }

    public static int getMax(int[] arr) {
        return process(arr, 0, arr.length - 1);
    }

    public static int process(int[] arr, int left, int right) {
        if (left == right) return arr[left];
        int mid = left + ((right - left) >> 1);
        return Math.max(process(arr, left, mid), process(arr, mid + 1, right));
    }
}
  1. 小和问题
  • 与传统merge算法区别在于左组的数等于右组时不产生小和,且把右组的数写入temp数组中
    小和问题题目
public class solution {
    public static void main(String[] args) {
        int[] array = {10,199, 2, 32, 41, 15, 22, 75, 140};
        int ans = getSmallSum(array);
        System.out.println(Arrays.toString(array));
        System.out.println(ans);
    }

    public static int getSmallSum(int[] arr) {
        int left = 0;
        int right = arr.length - 1;
        int ans = process(arr, left, right);
        return ans;
    }

    public static int process(int[] arr, int left, int right) {
        if (left == right) return 0;
        int mid = left + ((right - left) >> 2);
        return process(arr, left, mid) + process(arr, mid + 1, right) + merge(arr, left, mid, right);
    }

    public static int merge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int i = 0;
        int p1 = left;
        int p2 = mid + 1;
        int sum = 0;
        while (p1 <= mid && p2 <= right) {
            sum += arr[p1] < arr[p2] ? (right - p2 + 1) * arr[p1] : 0;
            temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= mid) {
            temp[i++] = arr[p1++];
        }
        while (p2 <= right) {
            temp[i++] = arr[p2++];
        }
        for (int j = 0; j < temp.length; ++j) {
            arr[left + j] = temp[j];
        }
        return sum;
    }
 }

  1. 逆序对
    在这里插入图片描述

三、快速排序

  • 时间复杂度O(N * logN),空间复杂度最差o(N),最好O(logN)
public class QuickSort {
    public static void main(String[] args) {
        int[] array = {5, 1, 1, 2, 0, 0};
        int[] ans = quictSort(array);
        System.out.println(Arrays.toString(ans));
    }

    public static int[] quictSort(int[] array) {
        sort(array, 0, array.length - 1);
        return array;
    }

    public static void sort(int[] arr, int left, int right) {
        if (left < right) {
        	// 随机选取一个数作为分割点,这样避免每次都最坏分割点产生,整体时间复杂度可达O(NlogN)
            swap(arr, left + (int)(Math.random() * (right - left + 1)), right);
            int[] p = partition(arr, left, right);
            sort(arr, left, p[0] - 1);
            sort(arr, p[1] + 1, right);
        }
    }

    public static int[] partition(int[] arr, int left, int right) {
        int less = left - 1; // 小于区域的右边界
        int more = right; // 大于区域的左边界
        while (left < more) { // left为当前元素位置,right为参考元素位置
            if (arr[left] < arr[right]) {
            	// 当当前元素小于参考元素时,将当前元素和小于区域右边第一个数交换,i++
                swap(arr, ++less, left++);
            }else if (arr[left] > arr[right]) {
            	// 当当前元素大于参考元素时,将当前元素和大于区域的左边第一个数交换,i不变
                swap(arr, --more, left);
            }else {
            	// 当当前元素等于参考元素时,i++
                left++;
            }
        }
        // 由于最后一个元素为参考元素始终没有移动,故在最后将其移动至等于区域的最后一位即大于区域的第一位,所以等于区域的边界变为less + 1和more
        swap(arr, more, right);
        return new int[]{less + 1, more};
    }


    public static void swap(int[] array, int temp, int end) {
        int val = array[temp];
        array[temp] = array[end];
        array[end] = val;
    }
}

四、堆排序

堆结构

  • 堆结构是用数组实现的完全二叉树结构
  • 堆结构用heapInsert和heapify两种操作
  • 优先级队列结构就是堆结构
  • 完全二叉树包含满二叉树
  • 完全二叉树:
    1. 左孩子:2 * i + 1
    2. 右孩子:2 * i + 2
    3. 父:(i - 1) / 2
  • 完全二叉树高度:logN + 1
  • 堆是完全二叉树,大根堆即以某个节点为根节点的二叉树的最大值为该根节点,小根堆同理

堆排序code

  • 时间复杂度O(NlogN),空间复杂度O(1)
public class heap_operate {
    public static void main(String[] args) {
        int[] array = {10, 199, 2, 32, 41, 15, 22, 75, 140};
        heapSort_v2(array);
        System.out.println(Arrays.toString(array));
    }

    public static void heapSort_v2(int[] arr) {
        if (arr == null || arr.length < 2) return;
        // 共N次循环,每次是O(logN)时间,故为NlogN
        for (int i = 0; i < arr.length; ++i) {
            heap_insert(arr, i);
        }
        // for (int i = 0; i < arr.length; --i) {
		//	 heapify(arr, i, arr.length);
		// } // 时间为O(N),比上面的方法更快变为大根堆,但是总时间复杂度不变仍为O(NlogN)
        int heapSize = arr.length;
        swap(arr, 0, --heapSize);
        while (heapSize > 0) {
            heapify(arr, 0, heapSize);
            swap(arr, 0, --heapSize);
        }
    }

    public static void heap_insert(int[] arr, int index){
        while (arr[index] > arr[(index - 1) / 2]) {
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }

    }

    public static void heapify(int[] arr, int index, int heapsize){
        int left = index * 2 + 1;
        while (left < heapsize) {
            int largest = left + 1 < heapsize && arr[left + 1] > arr[left] ? left + 1 : left;
            largest = arr[largest] > arr[index] ? largest : index;
            if (largest == index) break;
            swap(arr, largest, index);
            index = largest;
            left = index * 2 + 1;
        }
    }

    public static void   swap(int[] arr, int left, int right){
        arr[left] = arr[left] ^ arr[right];
        arr[right] = arr[left] ^ arr[right];
        arr[left] = arr[left] ^ arr[right];
    }
}

堆排序题目

在这里插入图片描述

public class Solution {
    public static void main(String[] args) {
        int[] arr = {6, 1, 3, 10, 2, 6, 3  , 8, 13, 9};
        sortedArrDistanceLessK(arr, 6);
        System.out.println(Arrays.toString(arr));
    }

    public static void sortedArrDistanceLessK(int[] arr, int k) {
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        int index = 0;
        for (; index <= Math.min(arr.length - 1, k); index++) {
            heap.add(arr[index]);
        }
        int i = 0;
        for (; index < arr.length; i++, index++) {
            heap.add(arr[index]);
            arr[i] = heap.poll();
        }
        while(!heap.isEmpty()) {
            arr[i++] = heap.poll();
        }
    }
}

五、基数排序

基数排序code

public class RadixSort_v2 {
    public static void main(String[] args) {
        int[] array = {10,199, 2, 32, 41, 15, 22, 75, 140};
        radixSortV2(array);
        System.out.println(Arrays.toString(array));
    }

    public static void radixSortV2(int[] arr) {
        if (arr == null || arr.length < 2) return;
        radixSortV2(arr, 0, arr.length - 1, maxbits(arr));
    }

    public static int maxbits(int[] arr) {
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; ++i) {
            max = Math.max(max, arr[i]);
        }
        int res = 0;
        while (max != 0) {
            res++;
            max /= 10;
        }
        return res;
    }

    public static void radixSortV2(int[] arr, int left, int right, int digit) {
        final int radix = 10;
        int i = 0, j = 0;
        int[] bucket = new int[right - left + 1];
        for (int d = 1; d <= digit; ++d) {
            int[] count = new int[radix];
            for (i = left; i <= right; ++i) {
                j = getDigit(arr[i], d);
                count[j]++;
            }
            for (i = 1; i < radix; ++i) {
                count[i] = count[i] + count[i - 1];
            }
            for (i = right; i >= left; --i) {
                j = getDigit(arr[i], d);
                bucket[count[j] - 1] = arr[i];
                count[j]--;
            }
            for (i = left, j = 0; i <= right; ++i, ++j) {
                arr[i] = bucket[j];
            }
        }
    }

    public static int getDigit(int x, int d) {
        return ((x / ((int)Math.pow(10, d - 1))) % 10);
    }
}

哈希表

  1. 哈希表使用层面上是一种集合结构
  2. 如果只有key没有伴随数据value,可以使用HashSet,如果有value,使用HashMap
  3. 有无伴随数据时HashMap和HashSet唯一的区别,底层的实际结构是一回事
  4. 哈希表增删改查(set:add, remove, contains; map: put, remove, put, containsKey, get)操作时间复杂度为O(1),但是常数时间比较大
  5. 放入哈希表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
  6. 放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用是这个东西内存地址的大小

有序表

  1. 有序表在使用层面上是一种集合结构
  2. java中时TreeSet和TreeMap
  3. 所有操作为时间复杂度为 O(logN)
  4. 常用操作:
    常用操作
    上图输出结果

单链表和双链表

重要技巧:

  • 额外数据结构记录,如哈希表,栈,队列
  • 快慢指针(可以找到链表中点)
  • 判断单个链表是否有环,如果有返回第一个入环节点。
    1. 方法一:设置一个快指针在head.next.next,一个慢指针在head.next,快指针一次走两步,慢指针一次走一步,第一次相遇后,将快指针重新置于head处,然后两个指针每次走一步,则相遇点为环形链表第一个入环节点
    2. 方法二:使用hashset,每次遍历一个节点,先判断set中是否存在该节点,如果存在则为第一个入环节点

题目

  1. 反转链表
  2. 回文链表
  3. 将单链表划分区域
    题目
  4. 复制含有随机指针节点的链表leetcode138
  5. 单链表相交问题
    题目
    1. 两个无环链表剑指offer-52
    2. 一个有环一个无环两链表不可能相交
    3. 两个有环节点
      在这里插入图片描述
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
    Node cur1 = null;
    Node cur2 = null;
    if (loop1 == loop2) {
        cur1 = head1;
        cur2 = head2;
        int n = 0;
        // 同两无环链表找交点问题,先走差值,以入环点为终点
        while (cur1 != loop1) {
            n++;
            cur1 = cur1.next;
        }
        while (cur2 != loop2) {
            n--;
            cur2 = cur2.next;
        }
        cur1 = n > 0 ? head1 : head2;
        cur2 = cur1 == head1 ? head2 : head1;
        n = Math.abs(n);
        while (n != 0) {
            n--;
            cur1 = cur1.next;
        }
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }else {
        cur1 = loop1.next;
        while (cur1 != loop1) {
            if (cur1 == loop2) {
                return loop1;
            }
            cur1 = cur1.next;
        }
        return null;
    }
}

二叉树

前序遍历(深度优先遍历)(中左右)

  • 递归
  • 迭代
    1. 从栈中弹出一个节点
    2. 处理该节点(打印)
    3. 先右后左(如果有的话)
    4. 循环周而复始
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if (root == null) return ans;
        Deque<TreeNode> dq = new ArrayDeque<TreeNode>();
        dq.offer(root);
        while (!dq.isEmpty()) {
            TreeNode tn = dq.pollLast();
            ans.add(tn.val);
            if (tn.right != null) dq.offer(tn.right);
            if (tn.left != null) dq.offer(tn.left);
        }
        return ans;
    }
}

中序遍历

  • 迭代
    1. 每颗子树,整棵树左边界进栈
    2. 依次弹出的过程中对弹出节点处理(打印)
    3. 对弹出节点的右树周而复始
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if (root == null) return ans;
        Deque<TreeNode> dq = new ArrayDeque<TreeNode>();
        while (!dq.isEmpty() || root != null) {
            if (root != null) {
                dq.offer(root);
                root = root.left;
            }else {
                root = dq.pollLast();
                ans.add(root.val);
                root = root.right;
            }
        }
        return ans;
    }
}

后序遍历(左中右)

  • 迭代
    1. 从主栈中弹出一个节点
    2. 把该节点放入收集栈中
    3. 然后先左后右
    4. 周而复始
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if (root == null) return ans;
        Deque<TreeNode> mainDq = new ArrayDeque<TreeNode>();
        Deque<TreeNode> collectDq = new ArrayDeque<TreeNode>();
        mainDq.offer(root);
        while (!mainDq.isEmpty()) {
            TreeNode node = mainDq.pollLast();
            collectDq.offer(node);
            if (node.left != null) mainDq.offer(node.left);
            if (node.right != null) mainDq.offer(node.right);
        }
        while (!collectDq.isEmpty()) {
            ans.add(collectDq.pollLast().val);
        }
        return ans;
    }
}

层序遍历(宽/广度优先遍历)

  • 使用队列
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        if (root == null) return ans;
        Deque<TreeNode> dq = new ArrayDeque<TreeNode>();
        dq.add(root);
        while (!dq.isEmpty()) {
            int size = dq.size();
            List<Integer> list = new ArrayList<Integer>();            
            while (size != 0) {
                TreeNode node = dq.poll();
                list.add(node.val);
                size--;
                if (node.left != null) dq.offer(node.left);
                if (node.right != null) dq.offer(node.right); 
            }
            ans.add(list);
        }
        return ans;
    }
}

搜索二叉树

  • 每一棵树都是左树比根节点小,右树大于根节点,即中序遍历为升序

满二叉树

  • 深度为L,节点个数为N,则N = 2 ^ L - 1

题目

  1. 二叉树宽度
  2. 验证二叉搜索树
  3. 平衡二叉树
  4. 满二叉树
  5. 完全二叉树
  6. 最低公共祖先(是否为BST)
  7. 二叉树找一个节点的后继节点
  8. 二叉树序列化和反序列化
  9. 折纸问题 微软面试题
    折纸问题题目
public class Solution {
    public void printAllFolds(int N) {
        // down为true时为凹折痕,false时为凸折痕
        printFolds(1, N, true);
    }

    // 打印折痕为中序遍历
    public void printFolds(int i, int N, boolean down) {
        if (i > N) return;
        printFolds(i + 1, N, true);
        System.out.println(down ? "凹" : "凸");
        printFolds(i + 1, N, false);
    }
}

基本类

  • 在笔试中遇到时将所给图结构转为自己的结构,相当于笔试中只写转换接口
  1. Graph:图有点集和边集
    Graph
  2. Edge:边有权重和来去的点
    Edge
  3. Node:点有自己的值和出度入度,还有指向的点以及out的边
    Node

在这里插入图片描述

题目

  1. 图的宽度优先遍历(相当于二叉树的层序遍历)
  2. 图的深度优先遍历
  • 迭代 用栈模拟
    图的深搜
  1. 拓扑排序
    题目要求

拓扑排序
4. Kruskal算法
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值