数据结构与算法第一期:栈,链表,队列

编程学习第一次作业

本周主要学习的数据结构有栈、队列和链表。需要实现一些基本的数据结构,和完成相应的LeetCode算法题。本作业都是通过Java编写的。

1.栈

1.1 编程实现特定需求

  • 用数组实现一个顺序栈
public class ArrayStack {
	private String[] items;
	private int count;
	private int n;
	
	//构造一个容量为n的Stack
	public ArrayStack(int n) {
		this.items = new String[n];
		this.n = n;
		this.count = 0;
	}
	
	public boolean push(String item) {
		if (count == n) {
			return false;
		}
		items[count] = item;
		count++;
		return true;
	}
	
	public String pop() {
		if (count == 0) {
			return null;
		}
		String item = items[count-1];
		items[count-1] = null;
		count--;
		return item;
	}
	
	//为浏览器的实现所添加的一个类
	public String showTop() {
		return items[count-1];
	}
	
	public void erase() {
		for (int i=0;i<n;i++) {
			items[i] = null;
		}
	}
	
	@Override
	public String toString() {
		return "ArrayStack [items=" + Arrays.toString(items) + ", count=" + count + ", n=" + n + "]";
	}
	//测试用例
	public static void main(String[] args) {
		ArrayStack stack = new ArrayStack(2);
		System.out.println("是否压入栈中: "+ stack.push("AAA"));
		System.out.println("是否压入栈中: "+ stack.push("BB"));
		System.out.println("是否压入栈中: "+ stack.push("CCC"));
		
		System.out.println(stack);
		System.out.println(stack.pop());
		System.out.println(stack.pop());
		System.out.println(stack.pop());
	}
}
  • 用链表实现一个链式栈

    此处实现用了泛型去处理,这样该链式栈可以存储各种数据类型的数据,具有通用性。

public class LinkedListStack {

	public Node head;
	public Node current;
	public int N;

	class Node<T> {
		public T data;
		public Node next;

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

	public <T> void push(T data) {
		if (head == null) {
			head = new Node(data);
			current = head;
		} else {
			current.next = new Node(data);
			current = current.next;
		}
		N++;
	}

	public <T> T pop() {
		T data = null;
		if (N == 0) {
		} else if (N == 1) {
			data = (T) head.data;
			head = null;
		} else {
			for (Node x = head; x != null; x = x.next) {
				if (x.next.next == null) {
					data = (T) x.next.data;
					x.next = null;
				}
			}
		}
		N--;
		return data;
	}

	public static <T> void main(String[] args) {
		LinkedListStack stack = new LinkedListStack();
		stack.push(2);
		stack.push("AAA");
		stack.push(23.2);
		System.out.println((T) stack.pop());
		System.out.println((T) stack.pop());
		System.out.println((T) stack.pop());
		System.out.println((T) stack.pop());
	}
}
  • 编程模拟实现一个浏览器的前进、后退功能
public class Browser {
	
	public static void main(String[] args) {
		ArrayStack browserStackX = new ArrayStack(4);
		ArrayStack browserStackY = new ArrayStack(4);
		// 记录浏览的网页,并将其压入X栈中,每次压入是执行清空Y栈内容操作
		browserStackX.push("www.baidu.com");
		browserStackY.erase();
		browserStackX.push("www.qq.com");
		browserStackY.erase();
		browserStackX.push("www.taobao.com");
		browserStackY.erase();
		//查看X栈栈顶元素,这就是当前浏览的网页taobao
		System.out.println("当前浏览的网页:"+browserStackX.showTop());
		//通过将taobao弹出并压入到Y栈中实现后退,到qq
		browserStackY.push(browserStackX.pop());
		System.out.println(browserStackX.showTop());
		//通过将qq弹出并压入到Y栈中实现后退,到baidu
		browserStackY.push(browserStackX.pop());
		//此时当前浏览的网页就是baidu
		System.out.println(browserStackX.showTop());
		
		System.out.println("**  打开一个新网页4399  **");
		//将4399压入X栈,并且清空Y栈,
		browserStackX.push("4399");
		browserStackY.erase();
		
		//此时当前浏览的网页就是4399,由于清空了Y栈,已经无法再回退到taobao,qq
		System.out.println("当前浏览的网页:"+browserStackX.showTop());
		System.out.println(browserStackX);
		System.out.println(browserStackY);
	}
}

1.2完成相应的LeetCode算法题

  • Valid Parentheses(有效的括号)20

    给定一个只包括 '('')''{''}''['']' 的字符串,判断字符串是否有效。

    有效字符串需满足:

    1. 左括号必须用相同类型的右括号闭合。
    2. 左括号必须以正确的顺序闭合。

    注意空字符串可被认为是有效字符串。

    思路:

    凡是涉及到括号的问题,都可以用栈,这一种数据结构去解决,此处是括号的匹配,因此开始遍历整个字符串,凡是遇到'(' 就往栈中压入与其对应的 ')' ,同理'{' ']' 也是做一样的处理。

    一旦遍历到的字符是括号的右半部分,则通过判断栈是否为空,以及栈中弹出的字符是否与遍历到的这个字符相等来返回是否有效,此处主要是返回无效的情况。

    最后,经过这一系列操作,如果是有效的括号字符串则最终栈应该为空,如若栈中还有东西,则说明出现了括号没有匹配的情况,即该括号字符串是无效的。通过返回stack.isEmpty() 来实现最后的判断。

    public boolean isValid(String s) {
    		if (s == null || s.length()==0)	return true;
    		Stack<Character> stack = new Stack<>();
    		for (int i=0; i < s.length(); i++) {
    			if (s.charAt(i) == '(') {
    				stack.push(')');
    			} else if (s.charAt(i) == '[') {
    				stack.push(']');
    			} else if (s.charAt(i) == '{') {
    				stack.push('}');
    			} else {
    				if (stack.isEmpty() || stack.pop() != s.charAt(i)) {
    					return false;
    				}
    			}
    		}
    		return stack.isEmpty();
    	}
    
  • Longest Valid Parentheses(最长有效的括号)32

    给定一个只包含 '('')' 的字符串,找出最长的包含有效括号的子串的长度。

    思路

    本题可以用两种解法,一种是DP还有一种就是Stack。DP较为复杂,较容易出错,所以此处是使用Stack来做。

    首先定义一个Stack,定义max,定义left:一组有效的括号的起始位置,初始值定为-1。

    栈里面存放的是Integer类型的数据。遍历字符串s,遇到 '(' 就将其对应的index压入栈中,当遇到 ')' 时,首先判断栈是否为空,如果为空,则表明之前并没有与其对应的 '(' ,因此这将可能是一个新的有效的括号的开始,因此将对应的index复制给left,用于记录下一个可能开始的有效的括号的左边界。

    如果不为空,则先将栈中pop出一个数值,如果pop出了之后为空,则表明这一组的有效括号没有结束,就将max的值从 maxi-stack.peek() 中找。

    如果为空,则表明这一组有效括号结束,则max的值是从 maxi-left 中去找。

    最后返回 max

    import java.util.Stack;
    
    public class LT32LongestValidParentheses {
    	public static int longestValidParentheses(String s) {
    		if (s == null || s.length() < 2) return 0;
    		int max = 0;
    		int left = -1;
    		Stack<Integer> stack = new Stack<>();
    		for (int i=0; i<s.length(); i++) {
    			if (s.charAt(i) == '(') {
    				stack.push(i);
    			} else {
    				if (stack.isEmpty()) {
    					left = i;
    				} else {
    					stack.pop();
    					if (stack.isEmpty()) {
    						max = Math.max(max, i-left);
    					} else {
    						max = Math.max(max, i-stack.peek());
    					}
    				}
    			}
    		}
    		return max;
    	}
    	//简单测试
    	public static void main(String[] args) {
    		int number = longestValidParentheses("()())()");
    		System.out.println(number);
    	}
    }
    
  • Evaluate Reverse Polish Notatio(逆波兰表达式求值) 150

    根据逆波兰表示法,求表达式的值。

    有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

    说明:

    • 整数除法只保留整数部分。
    • 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

    思路

    本题也是使用栈来做,由于题目中给出的说明:给定的波兰表达式总是有效的,所以就极大的方便了解题。

    先判断压入的符号是否为符号,如果为符号就弹出栈中的两个数进行运算,+* 的结果与顺序无关,比较好处理,直接运算完了再压入栈中即可,但是 -/ 的运算有先后顺序,直接弹出来进行运算的顺序与题目规定的刚好相反,因此需要对其进行处理,需要定义两个变量来暂存弹出来的数。

    运算完毕后,直接将栈中的数弹出,即可完成计算。

    import java.util.Stack;
    
    public class LT150EvaluateReversePolishNotatio {
    	public static int evalRPN(String[] tokens) {
    		Stack<Integer> stack = new Stack<>();
    		int s1 = 0;
    		int s2 = 0;
    		for (String s : tokens) {
    			if (s.equals("+")) {
    				stack.push(stack.pop() + stack.pop());
    			} else if (s.equals("-")) {
    				s1 = stack.pop();
    				s2 = stack.pop();
    				stack.push(s2 - s1);
    			} else if (s.equals("*")) {
    				stack.push(stack.pop() * stack.pop());
    			} else if (s.equals("/")) {
    				s1 = stack.pop();
    				s2 = stack.pop();
    				stack.push(s2 / s1);
    			} else {
    				stack.push(Integer.parseInt(s));
    			}
    		}
    		return stack.pop();
    	}
    	//简单测试样例
    	public static void main(String[] args) {
    		String[] tokens = {"2","1","+","3","*"};
    		int s = evalRPN(tokens);
    		System.out.println(s);
    	}
    }
    

2.队列

2.1 用数组实现一个顺序队列

public class ArrayQueue {
	
	private String[] items;
	private int n = 0;
	private int head = 0;
	private int tail = 0;
	//初始化队列
	public ArrayQueue(int capacity) {
		items = new String[capacity];
		n= capacity;
	}
	//入队,在head!=0,而tail=n时触发搬移操作
	public boolean enqueue(String item) {
		if (tail == n) {
			if (head == 0) {
				return false;
			}
			for (int i=head; i< tail; i++) {
				items[i-head] = items[i];
			}
			tail -= head;
			head = 0;
		}
		items[tail] = item;
		tail++;
		return true;
		
	}
	//出队
	public String dequeue() {
		if (head == tail) {
			return null;
		}
		String ret = items[head];
		head++;
		return ret;
	}
}

2.2 用链表实现一个链式队列

public class LinkedListQueue {
	
	private static class Node<T> {
		public T data;
		public Node next;

		public Node(T data, Node next) {
			this.data = data;
			this.next = next;
		}
	}
	
	private Node head = null;
	private Node tail = null;
	
	public <T> void enqueue(T data) {
		if (tail == null) {
			Node newNode = new Node<T>(data, null);
			head = newNode;
			tail = newNode;
		} else {
			tail.next = new Node<T>(data, null);
			tail = tail.next;
		}
	}
	
	public <T> T dequeue() {
		if (head == null) {
			return null;
		}
		T res = (T) head.data;
		head = head.next;
		//判断链表是否为空
		if (head == null) {
			tail = null;
		}
		return res;
	}
}

2.3 实现一个循环队列

public class CirculateQueue {
	private String[] items;
	private int n = 0;
	private int head = 0;
	private int tail = 0;
	//初始化队列
	public CirculateQueue(int capacity) {
		items = new String[capacity];
		n= capacity;
	}
	//入队,在head!=0,而tail=n时触发搬移操作
	public boolean enqueue(String item) {
		if ((tail+1)%n==head) {
			return false;
		}
		items[tail] = item;
		tail = (tail + 1) % n;
		return true;		
	}
	//出队
	public String dequeue() {
		if (head == tail) {
			return null;
		}
		String ret = items[head];
		head = (head + 1) % n;
		return ret;
	}
	//简单测试代码
	public static void main(String[] args) {
		CirculateQueue cq = new CirculateQueue(4);
		System.out.println(cq.enqueue("a"));
		System.out.println(cq.enqueue("b"));
		System.out.println(cq.enqueue("c"));
		System.out.println(cq.enqueue("d"));
		System.out.println(cq.dequeue());
		System.out.println(cq.enqueue("newA"));
		System.out.println(cq.dequeue());
		System.out.println(cq.dequeue());
		System.out.println(cq.dequeue());
		System.out.println(cq.dequeue());
	}
}

2.4 Design Circular Deque(设计一个双端队列)641

思路

就是一个简单的队列,注意在编写的时候,注意head和tail的变化。

class MyCircularDeque {

    private int[] items;
	private int k;
	private int head = 0;
	private int tail = 0;
	
	
	/** Initialize your data structure here. Set the size of the deque to be k. */
    public MyCircularDeque(int k) {
        items = new int[k];
        this.k = k;
    }
    
    /** Adds an item at the front of Deque. Return true if the operation is successful. */
    public boolean insertFront(int value) {
        if (tail - head == k) {
			return false;
		}
        if (head == 0) {
			for(int i=tail; i>0; i--) {
				items[i] = items[i-1];
			}
		tail++;
		items[0] = value;
		} else {
			items[head - 1] = value;
			head--;
		}
        return true;
    }
    
    /** Adds an item at the rear of Deque. Return true if the operation is successful. */
    public boolean insertLast(int value) {
        if (tail - head == k) {
			return false;
		} else if (tail == k) {
			for (int i=head; i<k-1; i++) {
				items[i - 1] = items[i];
			}
			items[k-1] = value;
            head--;
		} else {
			items[tail] = value;
			tail++;
		}
        return true;
    }
    
    /** Deletes an item from the front of Deque. Return true if the operation is successful. */
    public boolean deleteFront() {
        if (tail-head == 0) {
			return false;
		} else {
			items[head] = 0;
			head++;
			return true;
		}
    }
    
    /** Deletes an item from the rear of Deque. Return true if the operation is successful. */
    public boolean deleteLast() {
        if (tail - head == 0) {
			return false;
		} else {
			items[tail-1] = 0;
			tail--;
			return true;
		}
    }
    
    /** Get the front item from the deque. */
    public int getFront() {
        if (tail-head == 0) {
			return -1;
		} else {
			return items[head];
		}
    }
    
    /** Get the last item from the deque. */
    public int getRear() {
        if (tail - head == 0) {
			return -1;
		} else {
			return items[tail-1];
		}
    }
    
    /** Checks whether the circular deque is empty or not. */
    public boolean isEmpty() {
        if (tail - head == 0) {
			return true;
		} else {
			return false;
		}
    }
    
    /** Checks whether the circular deque is full or not. */
    public boolean isFull() {
        if (tail==k && head==0) {
			return true;
		} else {
			return false;
		}
    }
}

2.5 Sliding Window Maximum(滑动窗口最大值)239

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。

返回滑动窗口最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

注意:

你可以假设 k 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。

思路

我们用双向队列可以在O(n)时间内解决这题。当我们遇到新的数时,将新的数和双向队列的末尾比较,如果末尾比新数小,则把末尾扔掉,直到该队列的末尾比新数大或者队列为空的时候才停止。

这样可以保证队列里的元素是从头到尾降序的,由于队列里只有窗口内的数,所以他们其实就是窗口内第一大,第二大,第三大…的数。保持队列里只有窗口内数的方法是每来一个新的把窗口最左边的扔掉,然后把新的加进去。

由于我们在加新数的时候,已经把很多没用的数给扔了,这样队列头部的数并不一定是窗口最左边的数。这里的技巧是,我们队列中存的是那个数在原数组中的下标,这样我们既可以知道这个数的值,也可以知道该数是不是窗口最左边的数。

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
         if(nums == null || nums.length == 0) return new int[0];
        LinkedList<Integer> deque = new LinkedList<Integer>();
        // 最终的输出的数组的容量是nums.length + 1 - k
        int[] res = new int[nums.length + 1 - k];
        for(int i = 0; i < nums.length; i++){
            // 每当新数进来时,如果发现队列头部的数的下标,是窗口最左边数的下标,则扔掉
            if(!deque.isEmpty() && deque.peekFirst() == i - k) deque.poll();
            // 把队列尾部所有比新数小的都扔掉,保证队列是降序的
            while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i])
                deque.removeLast();
            // 加入新数
            deque.offerLast(i);
            // 队列头部就是该窗口内第一大的
            if((i + 1) >= k) res[i + 1 - k] = nums[deque.peek()];
        }
        return res;
    }
}

3 链表

3.1 实现单链表、循环链表、双向链表,支持增删改查

单链表

public class SinglyLinkedList {
	private Node head = null;

    public Node findByValue(int value) {
        Node p = head;
        while (p != null && p.data != value) {
            p = p.next;
        }
        return p;
    }

    public Node findByIndex(int index) {
        Node p = head;
        int pos = 0;
        while (p != null && pos != index) {
            p = p.next;
            ++pos;
        }
        return p;
    }

    //无头结点
    //表头部插入
    //这种操作将于输入的顺序相反,逆序
    public void insertToHead(int value) {
        Node newNode = new Node(value, null);
        insertToHead(newNode);
    }

    public void insertToHead(Node newNode) {
        if (head == null) {
            head = newNode;
        } else {
            newNode.next = head;
            head = newNode;
        }
    }

    //顺序插入
    //链表尾部插入
    public void insertTail(int value){
        Node newNode = new Node(value, null);
        //空链表,可以插入新节点作为head,也可以不操作
        if (head == null){
            head = newNode;

        }else{
            Node q = head;
            while(q.next != null){
                q = q.next;
            }
            newNode.next = q.next;
            q.next = newNode;
        }
    }
    public void insertAfter(Node p, int value) {
        Node newNode = new Node(value, null);
        insertAfter(p, newNode);
    }

    public void insertAfter(Node p, Node newNode) {
        if (p == null) return;

        newNode.next = p.next;
        p.next = newNode;
    }

    public void insertBefore(Node p, int value) {
        Node newNode = new Node(value, null);
        insertBefore(p, newNode);
    }

    public void insertBefore(Node p, Node newNode) {
        if (p == null) return;
        if (head == p) {
            insertToHead(newNode);
            return;
        }

        Node q = head;
        while (q != null && q.next != p) {
            q = q.next;
        }

        if (q == null) {
            return;
        }

        newNode.next = p;
        q.next = newNode;

    }

    public void deleteByNode(Node p) {
        if (p == null || head == null) return;

        if (p == head) {
            head = head.next;
            return;
        }

        Node q = head;
        while (q != null && q.next != p) {
            q = q.next;
        }

        if (q == null) {
            return;
        }

        q.next = q.next.next;
    }

    public void deleteByValue(int value) {
        if (head == null) return;

        Node p = head;
        Node q = null;
        while (p != null && p.data != value) {
            q = p;
            p = p.next;
        }

        if (p == null) return;

        if (q == null) {
            head = head.next;
        } else {
            q.next = q.next.next;
        }

        // 可重复删除指定value的代码
        /*
           if (head != null && head.data == value) {
           head = head.next;
           }
           Node pNode = head;
           while (pNode != null) {
           if (pNode.next.data == data) {
           pNode.next = pNode.next.next;
           continue;
           }
           pNode = pNode.next;
           }
         */
    }

    public void printAll() {
        Node p = head;
        while (p != null) {
            System.out.print(p.data + " ");
            p = p.next;
        }
        System.out.println();
    }

    //判断true or false
    public boolean TFResult(Node left, Node right){
        Node l = left;
        Node r = right;

        System.out.println("left_:"+l.data);
        System.out.println("right_:"+r.data);
        while(l != null && r != null){
           if (l.data == r.data){
               l = l.next;
               r = r.next;
               continue;
           }else{
               break;
           }

        }

        System.out.println("什么结果");
        if (l==null && r==null){
           System.out.println("什么结果");
           return true;
        }else{
           return false;
        }
    }
    // 判断是否为回文 

    public boolean palindrome(){
       if (head == null){
           return false;
       }else{
           System.out.println("开始执行找到中间节点");
           Node p = head;
           Node q = head;
           if (p.next == null){
               System.out.println("只有一个元素");
               return true;
           }
           while( q.next != null && q.next.next != null){
               p = p.next;
               q = q.next.next;

           }

           System.out.println("中间节点" + p.data);
           System.out.println("开始执行奇数节点的回文判断");
           Node leftLink = null;
           Node rightLink = null;
           if(q.next == null){
               // p 一定为整个链表的中点,且节点数目为奇数
               rightLink = p.next;
               leftLink = inverseLinkList(p).next;
               System.out.println("左边第一个节点"+leftLink.data);
               System.out.println("右边第一个节点"+rightLink.data);

           }else{
               //p q 均为中点
               rightLink = p.next;
               leftLink = inverseLinkList(p);
           }
           return TFResult(leftLink, rightLink);

       }
    }

    //带结点的链表翻转
    public Node inverseLinkList_head(Node p){
        // Head 为新建的一个头结点
        Node Head = new Node(9999,null);
        // p 为原来整个链表的头结点,现在Head指向 整个链表
        Head.next = p;
        /*
        带头结点的链表翻转等价于
        从第二个元素开始重新头插法建立链表
        */
        Node Cur = p.next;
        p.next = null;
        Node next = null;

        while(Cur != null){
            next = Cur.next;
            Cur.next = Head.next;
            Head.next = Cur;
            System.out.println("first " + Head.data);

            Cur = next;
        }

        // 返回左半部分的中点之前的那个节点
        // 从此处开始同步像两边比较
        return Head;

    }

    //无头结点的链表翻转
    public Node inverseLinkList(Node p){

        Node pre = null;
        Node r = head;
        System.out.println("z---" + r.data);
        Node next= null;
        while(r !=p){
            next = r.next;

            r.next = pre;
            pre = r;
            r = next;
        }

        r.next = pre;
        // 返回左半部分的中点之前的那个节点
        // 从此处开始同步像两边比较
        return r;

    }
    
    public static Node createNode(int value) {
        return new Node(value, null);
    }

    public static class Node {
        private int data;
        private Node next;

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

        public int getData() {
            return data;
        }
    }
    public static void main(String[]args){

        SinglyLinkedList link = new SinglyLinkedList(); 
        System.out.println("hello");
        //int data[] = {1};
        //int data[] = {1,2};
        //int data[] = {1,2,3,1};
        //int data[] = {1,2,5};
        //int data[] = {1,2,2,1};
       // int data[] = {1,2,5,2,1};
        int data[] = {1,2,5,3,1};

        for(int i =0; i < data.length; i++){
            //link.insertToHead(data[i]);
            link.insertTail(data[i]);
        }
       // link.printAll();
       // Node p = link.inverseLinkList_head(link.head);
       // while(p != null){
       //     System.out.println("aa"+p.data);
       //     p = p.next;
       // }

        System.out.println("打印原始:");
        link.printAll();
        if (link.palindrome()){
            System.out.println("回文");
        }else{
            System.out.println("不是回文");
        }
    }

}


时间有限,未完待续。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值