新手入门篇-异或、链表、队列、栈、递归

415 阅读6分钟

“这是我参与更文挑战的第21天,活动详情查看: 更文挑战

上一篇是数据结构与算法新手入门第六篇,本篇是数据结构与算法新手入门第七篇,快来体验一下算法的魅力吧!

认识异或运算

异或运算:相同为0,不同为1。异或运算可以理解为无进位相加!

基本用法

例子1

int a = 8;
int b = 2;
// 1000
// 0010
// 1010 = 10
System.out.println(a ^ b);

例子2

无进位相加可以理解为1+1=2,但是不进位,所以遇到满二进一,不进位,认为等于0即可。

int a = 11;
int b = 3;
// 1011
// 0011
// 1000 = 8
System.out.println(a ^ b);

异或运算的性质

  • 0 ^ N == N
  • N ^ N == N
  • 异或运算满足交换律和结合率

上面的性质用无进位相加来理解就非常的容易

题目一

如何不用额外变量交换两个数

// 如何不用额外变量交换两个数
public static void main(String[] args) {
    int a = 10;
    int b = 20;
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    System.out.println("a=" + a);
    System.out.println("b=" + b);
}

题目二

一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数。

思路:异或满足交换律,也就是跟位置无关,其次偶数次的数异或必为0,奇数次的数异或必为那个数。

public static void printOddTimesNum(int[] arr) {
    int eor = 0;
    for (int i = 0; i < arr.length; i++) {
        eor ^= arr[i];
    }
    System.out.println(eor);
}

题目三

怎么把一个int类型的数,提取出最右侧的1来?而不是采用遍历求解。

思路:先对这个数取反,然后加1,在&

rightOne = a & (-a)

一个数的反数等价于(~a) + 1,所以rightOne = a & ((~a) + 1)

int a = 6;
// 0110
        // 取反
// 1001 
        // 加1
// 1010

// 0110
        // &
// 1010
// 0010

题目四

给定一个int类型的数,统计二进制位上有多少个1?

思路:先求出最右侧的1,然后把最右侧的1去掉,依次再求出最右侧的1,再去掉最右侧的1。

// 求一个数的二进制位上有多少个1
public static int bit1Count(int num) {
    int count = 0;
    while (num != 0) {
        int rightOne = num & -num; // num & ((~num) + 1)
        count++;
        num ^= rightOne;// 每次去掉最右侧的1
    }
    return count;
}

题目五

一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两种数?

思路:出现偶数次的数异或后必为0,假设出现奇数次的两个数分别为a、b,如果数组中每个数都异或下,最后的结果必然是a ^ b,必然退出a!=b,如果a等于b,则a^b == 0了,所以a不等于b,必然在某个二进制位上a和b位置上必然为0或1,假设第二位上,a的第二位上为0,则b的第二位上则为1,或者a的第二位上为1,则b的第二位上则为0,只会出现这两种情况。假设数组中所有数的第二位上都异或下,必然所有数的第二位上的结果不是0就是1,假设a的第二位上是1,则b的第二位上0,则a^b^其他所有数的结果不等于0的,必然是a,则b等于a^b^a。是不是很晕,看下面的代码吧!

// arr中,有两种数,出现奇数次
public static void printOddTimesNum(int[] arr) {
    int eor = 0;
    for (int i = 0; i < arr.length; i++) {
        eor ^= arr[i];
    }
    // eor = a^b
    // eor != 0
    // eor某位置上必然存在1,把最右侧的1提取出来
    int rightOne = eor & (-eor);
    int onlyOne = 0;
    for (int i = 0; i < arr.length; i++) {
        if ((arr[i] & rightOne) != 0) {
            onlyOne ^= arr[i];
        }
    }
    System.out.println(onlyOne + " " + (onlyOne ^ eor));
}

链表

前边有介绍链表的反转、单链表反转、双链表反转。

单链表中把给定的值都删掉

思路:分两种情况,第一种情况删掉的节点正好是头部。这种情况直接把头部指针移动到不需要删除的位置上即可。第二种情况,要删除的节点在链表中间,因为是单链表,单链表的只有next,没有pre,所以需要用一个指针记录当前节点的前一个节点。

在双链表中把指定的值都删掉,思路类似,仅仅多了一个指针而已。

public class Code02_DeleteGivenValue {

    public static class Node {
        int val;
        Node next;

        public Node(int data) {
            this.val = data;
        }
    }

    // 把给定的值都删除
    public static Node deleteAllGivenValue(Node head, int val) {
        // head来到第一个不需要删的位置
        while (head != null) {
            if (head.val != val) {
                break;
            }
            head = head.next;
        }
        Node pre = head;
        Node cur = head;
        while (cur != null) {
            if (cur.val == val) {
                pre.next = cur.next;
            } else {
                pre = cur;
            }
            cur = cur.next;
        }
        return head;
    }

    public static void main(String[] args) {
        // 1 2 1 1 3 2
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        node1.next = node2;
        Node node11 = new Node(1);
        node2.next = node11;
        Node node111 = new Node(1);
        node11.next = node111;
        Node node3 = new Node(3);
        node111.next = node3;
        node3.next = new Node(2);

        Node head = deleteAllGivenValue(node1, 1);
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
        System.out.println();
    }
}

队列

队列的特性:先进先出,好似排队。

单链表实现队列

// 单项链表实现队列 尾部添加、头部取出,即满足先进先出
public static class Node<T> {
    T val;
    Node<T> next;

    public Node(T val) {
        this.val = val;
    }
}

public static class MyQueue<T> {
    Node<T> head;
    Node<T> tail;
    int size;

    public boolean isEmpty() {
        return size == 0;
    }

    public void offer(T data) {
        Node cur = new Node(data);
        if (tail == null) {
            head = tail = cur;
        } else {
            tail.next = cur;
            tail = cur;
        }
        size++;
    }

    public T poll() {
        T ans = null;
        if (head != null) {
            ans = head.val;
            head = head.next;
            size--;
        } else {
            tail = null;
        }
        return ans;
    }

    public T peek() {
        if (head == null) {
            return null;
        }
        return head.val;
    }
}

双链表实现队列

什么是双端队列?

双端队列:头部即可添加也可取出,尾部即可添加也可取出。

队列满足先进先出,即头部添加,尾部取出。或者尾部添加,头部取出都满足队列特性。

    public static class DoubleNode<T> {
        T val;
        DoubleNode<T> pre;
        DoubleNode<T> next;

        public DoubleNode(T data) {
            this.val = data;
        }
    }

    // 队列
    public static class MyQueue<T> {
        MyDeque<T> deque;

        public MyQueue() {
            this.deque = new MyDeque<>();
        }

        // 添加
        public void offer(T data) {
            deque.offerFromHead(data);
        }

        // 取出
        public T poll() {
            return deque.pollFromTail();
        }
    }

    // 双链表实现双端队列(头部既可以添加也可以取出,尾部既可以添加也可以取出)
    public static class MyDeque<T> {
        DoubleNode<T> head;
        DoubleNode<T> tail;
        int size;

        public boolean isEmpty() {
            return size == 0;
        }

        // 头部添加
        public void offerFromHead(T data) {
            DoubleNode cur = new DoubleNode(data);
            if (head == null) {
                head = tail = cur;
            } else {
                cur.next = head;
                head.pre = cur;
                head = cur;
            }
            size++;
        }

        // 头部取出
        public T pollFromHead() {
            T ans = null;
            if (head == null) {
                return ans;
            }
            size--;
            ans = head.val;
            if (head == tail) {
                head = tail = null;
            } else {
                head = head.next;
                head.pre = null;
            }
            return ans;
        }

        // 查看头部
        public T peekHead() {
            return head != null ? head.val : null;
        }

        // 尾部添加
        public void offerFromTail(T data) {
            DoubleNode cur = new DoubleNode(data);
            if (head == null) {
                head = tail = cur;
            } else {
                tail.next = cur;
                cur.pre = tail;
                tail = cur;
            }
            size++;
        }

        // 尾部取出
        public T pollFromTail() {
            T ans = null;
            if (head == null) {
                return ans;
            }
            size--;
            ans = tail.val;
            if (head == tail) {
                head = tail = null;
            } else {
                tail = tail.pre;
                tail.next = null;
            }
            return ans;
        }

        // 查看尾部
        public T peekTail() {
            return tail != null ? tail.val : null;
        }
    }

数组实现队列

数组的特性,一旦初始化,数组的大小就固定了,可以使用技巧让数组可以重复利用,即环形数组。

// 数组实现队列 先进先出(尾部添加、头部取出)
public static class MyQueue<T> {
    private Object[] arr;
    private int offerIndex;
    private int pollIndex;
    private int size;
    private final int limit;

    public MyQueue(int limit) {
        this.arr = new Object[limit];
        this.offerIndex = 0;
        this.pollIndex = 0;
        this.size = 0;
        this.limit = limit;
    }

    public boolean isFull() {
        return size == limit;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    // 入队
    public void offer(Object data) {
        if (isFull()) {
            throw new RuntimeException("队列已满,不能再添加了!");
        }
        size++;
        arr[offerIndex] = data;
        offerIndex = nextIndex(offerIndex);
    }

    // 出队
    public T poll() {
        if (isEmpty()) {
            throw new RuntimeException("队列已空,不能再取出了!");
        }
        size--;
        T ans = (T) arr[pollIndex];
        pollIndex = nextIndex(pollIndex);
        return ans;
    }

    // 如果现在的下标是i,返回下一个位置
    private int nextIndex(int i) {
        return i < limit - 1 ? i + 1 : 0;
    }
}

如何用队列结构实现栈结构

思路:栈的特性先进后出或后进先出,可以使用两个队列,入栈用queue正常放,出栈的话,需要help辅助队列记录剩余的数。然后交换位置。

// 用队列结构实现栈结构
public static class MyStack<T> {
    private Queue<T> queue;
    private Queue<T> help;

    public MyStack() {
        this.queue = new LinkedList<>();
        this.help = new LinkedList<>();
    }

    // 入栈
    public void push(T val) {
        queue.offer(val);
    }

    // 出栈
    public T pop() {
        while (queue.size() > 1) {
            help.offer(queue.poll());
        }
        T ans = queue.poll();
        Queue<T> tmp = queue;
        queue = help;
        help = tmp;
        return ans;
    }

    public T peek() {
        while (queue.size() > 1) {
            help.offer(queue.poll());
        }
        T ans = queue.poll();
        help.offer(ans);
        Queue<T> tmp = queue;
        queue = help;
        help = tmp;
        return ans;
    }

}

栈的特性:先进后出,犹如弹匣。

单链表实现栈

// 单向链表实现栈,先进后出,后进先出,头部添加、头部移除即可实现栈
public static class Node<T> {
    T val;
    Node<T> next;

    public Node(T val) {
        this.val = val;
    }
}

public static class MyStack<T> {
    Node<T> head;
    int size;

    public boolean isEmpty() {
        return size == 0;
    }

    public void push(T data) {
        Node cur = new Node(data);
        if (head != null) {
            cur.next = head;
        }
        head = cur;
        size++;
    }

    public T pop() {
        T ans = null;
        if (head != null) {
            ans = head.val;
            head = head.next;
            size--;
        }
        return ans;
    }
}

双链表实现栈

栈:先进后出或者后进先出。

双端队列实现栈:尾部添加、尾部取出。或者头部添加、头部取出都满足栈特性。

public static class DoubleNode<T> {
    T val;
    DoubleNode<T> pre;
    DoubleNode<T> next;

    public DoubleNode(T data) {
        this.val = data;
    }
}

// 栈
public static class MyStack<T> {
    MyDeque<T> deque;

    public MyStack() {
        this.deque = new MyDeque<>();
    }

    // 入栈
    public void push(T data) {
        deque.offerFromTail(data);
    }

    // 出栈
    public T pop() {
        return deque.pollFromTail();
    }
}

// 双链表实现双端队列(头部既可以添加也可以取出,尾部既可以添加也可以取出)
public static class MyDeque<T> {
    DoubleNode<T> head;
    DoubleNode<T> tail;
    int size;

    public boolean isEmpty() {
        return size == 0;
    }

    // 头部添加
    public void offerFromHead(T data) {
        DoubleNode cur = new DoubleNode(data);
        if (head == null) {
            head = tail = cur;
        } else {
            cur.next = head;
            head.pre = cur;
            head = cur;
        }
        size++;
    }

    // 头部取出
    public T pollFromHead() {
        T ans = null;
        if (head == null) {
            return ans;
        }
        size--;
        ans = head.val;
        if (head == tail) {
            head = tail = null;
        } else {
            head = head.next;
            head.pre = null;
        }
        return ans;
    }

    // 查看头部
    public T peekHead() {
        return head != null ? head.val : null;
    }

    // 尾部添加
    public void offerFromTail(T data) {
        DoubleNode cur = new DoubleNode(data);
        if (head == null) {
            head = tail = cur;
        } else {
            tail.next = cur;
            cur.pre = tail;
            tail = cur;
        }
        size++;
    }

    // 尾部取出
    public T pollFromTail() {
        T ans = null;
        if (head == null) {
            return ans;
        }
        size--;
        ans = tail.val;
        if (head == tail) {
            head = tail = null;
        } else {
            tail = tail.pre;
            tail.next = null;
        }
        return ans;
    }

    // 查看尾部
    public T peekTail() {
        return tail != null ? tail.val : null;
    }
}

数组实现栈

正常使用即可。

// 数组实现栈 先进后出
public static class MyStack<T> {
    private Object[] arr;
    private int index = 0;
    private int size = 8;

    public MyStack(int size) {
        this.size = size;
        this.arr = new Object[size];
    }

    public boolean isEmpty() {
        return index == 0;
    }

    public boolean isFull() {
        return index == size;
    }

    // 添加
    public void push(Object data) {
        if (isFull()) {
            throw new RuntimeException("栈满了,不能再添加了~");
        }
        arr[index++] = data;
    }

    // 弹出
    public T pop() {
        if (isEmpty()) {
            throw new RuntimeException("栈空了,不能再弹出了~");
        }
        return (T) arr[--index];
    }
}

实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能

  • pop、push、getMin操作的时间复杂度都是 O(1)。
  • 设计的栈类型可以使用现成的栈结构。

思路:采用两个栈,一个栈正常存放数,另一个栈存栈中最小数。

// 实现一个特殊的栈,在基本功能的基础上,在实现返回栈中的最小元素的功能
public static class MyStack1 {
    private Stack<Integer> stackData;
    private Stack<Integer> stackMin;

    public MyStack1() {
        this.stackData = new Stack<>();
        this.stackMin = new Stack<>();
    }

    // 添加
    public void push(int newNum) {
        if (stackMin.isEmpty()) {
            stackMin.push(newNum);
        } else if (newNum <= getMin()) {
            stackMin.push(newNum);
        }
        stackData.push(newNum);
    }

    // 弹出
    public int pop() {
        if (stackData.isEmpty()) {
            throw new RuntimeException("Your stack is Empty.");
        }
        int val = stackData.pop();
        if (val == getMin()) {
            stackMin.pop();
        }
        return val;
    }

    public int getMin() {
        if (stackMin.isEmpty()) {
            throw new RuntimeException("Your stack is Empty.");
        }
        return stackMin.peek();
    }
}

public static class MyStack2 {
    private Stack<Integer> stackData;
    private Stack<Integer> stackMin;

    public MyStack2() {
        this.stackData = new Stack<>();
        this.stackMin = new Stack<>();
    }

    public void push(int newNum) {
        if (stackMin.isEmpty()) {
            stackMin.push(newNum);
        } else if (newNum < getMin()) {
            stackMin.push(newNum);
        } else {
            int val = getMin();
            stackMin.push(val);
        }
        stackData.push(newNum);
    }

    public int pop() {
        if (stackData.isEmpty()) {
            throw new RuntimeException("Your stack is Empty.");
        }
        stackMin.pop();
        return stackData.pop();
    }

    public int getMin() {
        if (stackMin.isEmpty()) {
            throw new RuntimeException("Your stack is Empty.");
        }
        return stackMin.peek();
    }
}

如何用栈结构实现队列结构

思路:可以采用两个栈结构,一个存的栈,一个出的栈。

// 用栈结构实现队列结构
public static class MyQueue {
    private Stack<Integer> stackPush;
    private Stack<Integer> stackPop;

    public MyQueue() {
        this.stackPush = new Stack<>();
        this.stackPop = new Stack<>();
    }

    // 入队
    public void offer(int val) {
        stackPush.push(val);
        pushToPop();
    }

    // 出队
    public int poll() {
        if (stackPush.isEmpty() && stackPop.isEmpty()) {
            throw new RuntimeException("Queue is empty!");
        }
        pushToPop();
        return stackPop.pop();
    }

    // 查看头部
    public int peek() {
        if (stackPush.isEmpty() && stackPop.isEmpty()) {
            throw new RuntimeException("Queue is empty!");
        }
        pushToPop();
        return stackPop.peek();
    }

    // push栈向pop栈倒入数据
    public void pushToPop() {
        if (stackPop.isEmpty()) {
            while (!stackPush.isEmpty()) {
                stackPop.push(stackPush.pop());
            }
        }
    }

}

递归

求数组arr[L...R]中的最大值,怎么用递归方法实现。

public static int getMax(int[] arr, int L, int R) {
    if (L == R) {
        return arr[L];
    }
    int mid = L + ((R - L) >> 1);
    return Math.max(getMax(arr, L, mid), getMax(arr, mid + 1, R));
}

总结

本篇开始介绍了异或的特性及一些骚操作,紧接着介绍了链表、队列、栈的常见考题,最后介绍了递归的基本使用。

欢迎大家关注公众号(MarkZoe)互相学习、互相交流。