数据结构(1)

本文介绍了数组的增删改查操作,包括动态扩容和缩容策略,以及栈和队列的基本概念和实现。详细阐述了栈的入栈、出栈、查看栈顶元素等操作,队列的入队、出队、查看队首元素等操作。同时,给出了LeetCode中涉及数组、栈和队列的典型问题,如两数之和、有效括号、删除重复元素等的解决方案。
摘要由CSDN通过智能技术生成

1.数组

  1. 数组的增删改查操作

//增加操作
  public void add(int index, T val) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("index is invalid!");
        }
        //判断数组是否满
        if(this.size==this.capacity){
            //数组满的话扩容原来数组的两倍
            int newCapacity=this.capacity*2;
            T[] newDate=(T[]) new Object[newCapacity];//创建一个新的数组
            //进行数组迁移
            for (int i = 0; i < this.size; i++) {
                newDate[i]=this.data[i];
            }
            this.capacity=newCapacity;
            this.data=newDate;
        }
//删除操作
       public T remove(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("index is invalid!");
        }
        T val = this.data[index];
        for (int i = index; i < this.size; i++) {
            this.data[i] = this.data[i+1];
        }
        this.size -= 1;
​
        if(this.size<=this.capacity/4&&this.capacity/2>1){
            int newCapacity=this.capacity/2;
            T[] newDate=(T[])new Object[newCapacity];
            for (int i = 0; i < this.size; i++) {
                newDate[i]=this.data[i];
            }
            this.capacity=newCapacity;
            this.data=newDate;
​
        }
        return val;
    }
//替换操作
          public void upLoad(T var, int index) {
        if (index < 0 || index > this.size) {
            throw new IllegalArgumentException("index is invalid!");
        }
        this.data[index] = var;
    }
//查找操作
      
        public int getEnByVal(int index, T val) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("index is invalid!");
        }
        for (int i = 0; i < this.size; i++) {
            if (this.data[i] == val) {
​
                return i;
            }
        }
        return -1;
    }  
    

  1. 数组的增加和删除操作:动态数组

    • 动态的进行扩容操作,一般默认为增加2*capacity,这样的增加不会浪费数组的储存空间.

    • 删除时:数组自动进行缩容操作(缩容的条件:当真正不需要时,才能进行缩容 size<=capacity/4) 自动缩容为capacity/2 (Lazy)避免复杂度震荡的问题

    //扩容操作
     if(this.size==this.capacity){
                //数组满的话扩容原来数组的两倍
                int newCapacity=this.capacity*2;
                T[] newDate=(T[]) new Object[newCapacity];//创建一个新的数组
                //进行数组迁移
                for (int i = 0; i < this.size; i++) {
                    newDate[i]=this.data[i];
                }
                this.capacity=newCapacity;
                this.data=newDate;
            }
    //缩容操作
         if(this.size<=this.capacity/4&&this.capacity/2>1){
                int newCapacity=this.capacity/2;
                T[] newDate=(T[])new Object[newCapacity];
                for (int i = 0; i < this.size; i++) {
                    newDate[i]=this.data[i];
                }
                this.capacity=newCapacity;
                this.data=newDate;
            }

  2. 泛型 T[]arr=(T[])(new Object[])

  3. 以Java中的array作为我们自己数组的底层数据容器 data

  4. 时间的复杂度O(n) , O(lgn) , O(n^2) n是趋于无穷

  5. 增加head O(n) tail O(1)均摊时间复杂度 指定位置O(n)==>O(n)(因为对数组遍历,故时间复杂度为n)

    删除head O(n) tail O(1)均摊时间复杂度 指定位置O(n)==>O(n)(上同)

    修改操作 O(1)(不需要对数组的遍历)

    查询遍历:遍历:O(n),通过索引获取指定位置的索引元素:O(1)(上同)

  6. 重点内容:处理数组的问题,都是在索引 上的做文章

2.栈和队列

1.什么是栈(Stack)?

  1. 栈是一种线性数据结构,规定只能只能在栈顶添加元素,也只能在栈顶取出元素(栈是一种先进后出的数据结构

     

  2. 栈的具体实现

    • void push(E) 入栈

    • E pop() 出栈

    • E peek() 站看栈顶元素

    • int getSize() 获取栈中的元素个数

    • boolean isEmpty() 判断栈是否为空

  3. 时间复杂度分析

    • void push() O(1)

    • E pop() O(1)

    • E peek() O(1)

    • int getSize() O(1)

    • boolean isEmpty() O(1)

  • 栈是一种先进后出的线性数据结构(有底子的杯子) 栈顶(操作元素)

  • 队列是一种先进先出的线性数据结构(排队做核酸) 队首(出口) 队尾(入口)

  • 栈:interface-->push pop peek isEmpty size

  • 队列:interface-->offer(入队)pool(出队) getFirst (查看队首元素) isEmpty

  • 栈和队列的实现类:以数组作为栈和队列的底层数据结构

  • 接口---->实现类(属性:data)---->数组

时间复杂度分析:数组 链表 二分搜索树

栈:push O(1) pop O(1)

队列: offer O(1) pool O(n)

package feifan.yc.stack;
​
import feifan.yc.arr.arrSub;
​
public class StackDemo<T> implements Stack<T> {
    //以我们封装的数组作为栈的底层数据结构
    private  arrSub<T>data;
​
    public StackDemo() {
        data=new arrSub<>();
    }
​
    public StackDemo(int capacity) {
        data=new arrSub<>(capacity);
    }
​
    @Override//弹栈
    public T pop() {
        return data.removeTail();
    }
​
    @Override//往栈中添加元素
    public void push(T val) {
        data.addTail(val);
    }
​
    @Override//查看元素个数
    public int size() {
        return data.getSize();//获得数组元素个数
    }
​
    @Override//判断是否为空
    public boolean isEmpty() {
        return data.isEmpty();
    }
​
    @Override//查看栈顶元素
    public T peek(T val) {
        return data.getEnByIndex(this.size() - 1);
    }
​
    @Override
    public T peek() {
        return null;
    }
​
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("栈顶[");
        T[] arr = data.getData();
        for (int i = size() - 1; i >= 0; i--) {
            sb.append(arr[i]);
            if (i != 0) {
                sb.append(",");
            }
        }
        sb.append("]栈底");
        return sb.toString();
    }
​
​
    public static void main(String[] args) {
        Stack<String> stack = new StackDemo<>(10);
​
        String[] fruits
                = {"apple", "banana", "peach", "watermelon", "strawberry", "pear", "orange", "grape"};
​
        for (int i = 0; i < fruits.length; i++) {
            stack.push(fruits[i]);
        }
        System.out.println(stack);
​
        while (!stack.isEmpty()) {
            System.out.println("ele-->" + stack.pop());
            System.out.println(stack + "栈中元素的个数:" + stack.size());
        }
    }
}

3.队列

1.什么是队列(Queue)?

  1. 队列是一种线性数据结构(先进先出)

  2.  

  3. 队列的实现

    • void offer() 入队

    • E pool() 出队

    • E peek() 查看列顶元素

    • int getSize() 查看队列的元素个数

    • boolean isEmpty() 判断队列是否为空

    package feifan.yc.queue;
    ​
    ​
    ​
    import java.util.Optional;
    ​
    public class ArrQueue<T> implements Queue<T> {
        // 队列的容器
        CycleArray<T> data;
    ​
        // 构造函数
        public ArrQueue() {
            data = new CycleArray<>();
        }
    ​
        public ArrQueue(int capacity) {
            this.data = new CycleArray<>(capacity);
        }
    ​
        @Override
        public void offer(T ele) {
            data.addTail(ele);
        }
    ​
        @Override
        public T poll() {
            return data.removeHead();
        }
    ​
        @Override
        public int size() {
            return data.getSize();
        }
    ​
        @Override
        public boolean isEmpty() {
            return data.isEmpty();
        }
    ​
        @Override
        public T peek() {
            Optional<T> optional = data.getFront();
            if (optional.isPresent()) {
                return optional.get();
            }
            return null;
        }
    ​
        @Override
        public String toString() {
            return data.toString();
        }
    }
    ​

2.什么是循环队列?

  1. 循环队列---->解决的是以数组作为队列的底层数据结构,在删除时,时间复杂度为 O(n),是在删除队首元素时,后面的元素要进行前移,为了让时间复杂度为O(1),后面的元素就不能进行移动.

  2.  

办法:声明两个索引

front队首 tail队尾

当删除时,队首的索引向后移动就可以了

队列为空时:front==tail

队列满:(tail+1)%capacity=front

package feifan.yc.queue;
​
import java.util.Optional;
​
​
/*解决了队列删除时的时间复杂度O(n),循环队列删除时的时间复杂度为O(1)
 * 假设有两个索引,front-->始终指该数组的第一个元素  tail-->始终指向元素插入位置
 * 如果front==tail时,该循环数组为空
 * 如果(tail+1)%capacity=front时,该数组为满状态
 *
 * */
/**
 * 循环队列的底层容器
 */
public class CycleArray<T> {
    // 数据容器
    private T[] data;
    // 容积
    private int capacity;
​
    // 实际存放元素的个数
    private int size;
​
    // 声明两个索引  front--队首 tail--队尾
    int front = 0;
    int tail = 0;
    // front === tail  队列是空的  (tail+1)/capacity == front  队列是满的
​
    public CycleArray() {
        this(10);
    }
​
    public CycleArray(int capacity) {
        this.capacity = capacity + 1;//因为要浪费一个空间,所以在这里要多加一个空间
        this.data = (T[]) new Object[this.capacity];// 浪费一个空间
        this.size = 0;
    }
​
    public T[] getData() {
        return this.data;
    }
​
    // 1、队列是否为空
    public boolean isEmpty() {
        return this.front == this.tail;
    }
​
    // 2、获取数组的容积
    public int getCapacity() {
        return this.capacity;
    }
​
    //3、获取实际存放元素的个数
    public int getSize() {
        return this.size;
    }
​
    // 在队列的尾部添加元素(tail指向待插入元素的位置)
    public void addTail(T val) {
        // 当队列已满,要进行扩容
        if ((this.tail + 1) % this.capacity == this.front) {
            // 新的容积
            int newCapacity = 2 * (this.capacity - 1);
            resize(newCapacity);
        }
​
        // 将val插入到队列的尾部
        this.data[this.tail] = val;
        // tail向后移动
        this.tail = (this.tail + 1) % capacity;
        // 更新size的值
        this.size += 1;
    }
​
​
    private void resize(int newCapacity) {
        T[] newData = (T[]) new Object[newCapacity + 1];
        // 迁移数据 this.front
        int cur = this.front;
        int index = 0;
        while (cur != this.tail) {
            newData[index++] = this.data[cur];
            cur = (cur + 1) % this.capacity;
        }
        // 修改属性
        this.capacity = newCapacity + 1;
        this.data = newData;
        this.front = 0;
        this.tail = this.size;
    }
​
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        int cur = this.front;
        while (cur != this.tail) {
            sb.append(this.data[cur]);
            if ((cur + 1) % this.capacity != this.tail) {
                sb.append(",");
            }
            cur = (cur + 1) % this.capacity;
        }
        sb.append("[" + this.front + "<-->" + this.tail + "]");
        return sb.toString();
    }
​
​
    //从队列中移除元素
    public T removeHead() {
        // 1、队列是否为空
        if (this.front == this.tail) {
            return null;
        }
        T val = this.data[this.front];
        this.front = (this.front + 1) % this.capacity;
        this.size--;
        // 缩容
        if (this.size <= this.capacity / 4 && this.capacity / 2 > 1) {
            resize(this.capacity / 2);
        }
        return val;
    }
​
    public Optional<T> getFront() {
        if (isEmpty()) {
            return Optional.empty();
        }
        return Optional.of(this.data[this.front]);
    }
​
​
//     测试
    public static void main(String[] args) {
        CycleArray<Integer> myArr = new CycleArray<>(5);
        for (int i = 1; i <= 15; i++) {
            myArr.addTail(i + 25);
            System.out.println("添加:" + myArr);
            if (i % 3 == 0) {
                int val = myArr.removeHead();
                System.out.println("删除: Header是:" + val);
            }
        }
    }
}

4.leetCode(练习题练习)

1.#1两数之和

//给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。
//暴力解法
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int [] arr=new int[2];//重新new一个长度为2的新的数组
        for(int i=0;i<nums.length;i++){//对原数组进行遍历,将所符合的元素的索引添加到新的数组中
            for(int j=i+1;j<nums.length;j++){
                if(nums[i]+nums[j]==target){
                    arr[0]=i;
                    arr[1]=j;
                }
            }
        }
        return arr;//返回新的数组
    }
}
//双指针
class Solution {
public int[] twoSum(int[] nums, int target) {
        int fast = 0;//设置一个快指针
        int slow = 0;//设置一个,慢指针
        int n = nums.length;
        int[] arr = new int[2];
        while (slow < n) {
            if (nums[fast] + nums[slow] == target && fast != slow) {
                arr[0] = slow;
                arr[1] = fast;
                break;
            }
            if (fast < n - 1) {
                fast++;
            }
            if (fast == n) {
                slow++;
            }
        }
        return arr;
}}

2.#20 有效地括号

//给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
//有效字符串需满足:
//左括号必须用相同类型的右括号闭合。
//左括号必须以正确的顺序闭合。
//每个右括号都有一个对应的相同类型的左括号。
​
class Solution {
    public boolean isValid(String s) {
       Stack<Character> stack=new StackDemo<Character>();//创建一个栈
      char [] arr=s.toCharArray();//将给定的的字符串转换为一个char类型的数组
        for (int i = 0; i < arr.length; i++) {//将char类型的数组进行遍历
            if(stack.isEmpty()){//如果栈是空的话,将数组中的元素压入栈中
                stack.push(arr[i]);
                continue;
            }
            char top =stack.peek();//将栈顶的元素赋给top
            if((top=='(')&&arr[i]==')'){//将遍历的数组与栈顶的元素进行比较
                stack.pop();
                continue;
            }
            if((top=='{')&&arr[i]=='}'){//将遍历的数组与栈顶的元素进行比较
                stack.pop();
                continue;
            }
            if((top=='[')&&arr[i]==']'){//将遍历的数组与栈顶的元素进行比较
                stack.pop();
                continue;
            }
           stack.push(arr[i]);
        }
     return stack.isEmpty();//返回栈中是否为空
    }
}

3.#27 移出元素

//给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
//不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
//元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素
​
class Solution {
    public int removeElement(int[] nums, int val) {
        int index=0;//设置一个指针
   for(int i=0;i<nums.length;i++){//对数组进行遍历
       if(nums[i]!=val){
         nums[index++]=nums[i];//进行
       }
   }
        return index;
​
    }
}

4.#26 删除有序数组中的有序数组

//给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
​
//由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
​
//将最终结果插入 nums 的前 k 个位置后返回 k 。
​
//不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
​
class Solution {
    public int removeDuplicates(int[] nums) {
         int index=0;
        for(int i=1;i<nums.length;i++){
            if(nums[i-1]!=nums[i]){
                nums[++index]=nums[i];
            }
        }
        return index+1;
}}

5.#682 棒球比赛

//你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。
//比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则:
//整数 x - 表示本回合新获得分数 x
//"+" - 表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。
//"D" - 表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。
//"C" - 表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。
//请你返回记录中所有得分的总和
​
class Solution {
    public int calPoints(String[] operations) {
        int n = operations.length;
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < n; i++) {
            if (operations[i].equals("+")) {
                list.add(list.get(list.size()-1)+ list.get(list.size()-2));
            } else if (operations[i].equals("D")) {
                list.add(list.get((list.size() - 1) )*2);
            } else if (operations[i].equals("C")) {
                list.remove(list.size() - 1);
            } else {
                list.add(Integer.valueOf(operations[i]));
            }
        }
        System.out.println(list);
        int sum=0;
        for(Integer s:list){
            sum+=s;
        }
​
         return sum;
}}

6.#1047 删除字符串中的所有相邻重复项

//给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
//在 S 上反复执行重复项删除操作,直到无法继续删除。
//在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
​
class Solution {
    public String removeDuplicates(String s) {
       int n=s.length();
       char[] a=s.toCharArray();
       char[] stack=new char[n];
       int top=-1; 
        for(int j=0;j<n;j++){
            if(top>-1&&stack[top]==a[j]){
                top--;
            }else{
                stack[++top]=a[j];
            }
        }
        char[] aar=new char[top+1];
        for(int i=0;i<top+1;i++){
            aar[i]=stack[i];
        }
        String str=new String(aar);
        return str;
    }
    
    }

7.#剑指 Offer 09 用两个栈实现队列

//用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
​
   class CQueue<Integer> {
        Stack<Integer> stack2;
        Stack<Integer> stack1;
        //stack1用来进队列的栈,stack2用来出队列的栈
        public CQueue() {
           stack1=new Stack<>();
           stack2=new Stack<>();
        }
​
        public void appendTail(Integer value) {
          stack1.push(value);
        }
​
        public int deleteHead() {
            //先要判断用来出队的栈是否为空,进队的栈中的元素需要全部出来之后才能进出队的栈才能实现先进先出
            if(stack2.isEmpty()){
                if(stack1.isEmpty()){return -1;} 
                while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
            }
             return (int) stack2.pop();
            }
        
    }
​
/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue obj = new CQueue();
 * obj.appendTail(value);
 * int param_2 = obj.deleteHead();
 */

8.#面试题59 - II 队列的最大值

//请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
//若队列为空,pop_front 和 max_value 需要返回 -1
​
​
class MaxQueue {
​
   // 定义一个队列
    Queue<Integer> queue;
    // 双端队列
    LinkedList<Integer> help;// 帮助队列
​
    // 构造函数(初始化)
    public MaxQueue() {
        queue = new LinkedList<>();
        help = new LinkedList<>();
    }
​
    // 求最大值
    public int max_value() {
        if (help.isEmpty()) {
            return -1;
        }
        return help.peekFirst();
    }
​
    // 入队
    public void push_back(int value) {
        queue.offer(value);
        // 添加到双端队列,要从双端队列 队尾移除比value小的元素,然后将value从队尾添加
        while (!help.isEmpty()) {
            if (help.peekLast() < value) {
                help.pollLast();
            } else {
                break;
            }
        }
        help.offerLast(value);
    }
​
    // 出队
    public int pop_front() {
        if (queue.isEmpty()) {
            return -1;
        }
        int val = queue.poll();
​
        // 出队的val值要与双端队列的队首元素做比较,如果相同,要从双端队列中移除
        if (val == help.peekFirst()) {
            help.pollFirst();
        }
        return val;
    }
}
​
/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue obj = new MaxQueue();
 * int param_1 = obj.max_value();
 * obj.push_back(value);
 * int param_3 = obj.pop_front();
 */

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃橘子的Crow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值