学点算法(一)——ArrayList内部数组实现元素去重

ArrayList中,内部实际存储的元素的是数组,要实现元素去重,即是对该数组进行去重。常规思路是借助外部数据结构,如HashMap等来实现数据查找是否存在后去重,这里我们来实现一个利用内部自身数组的去重方法。

为了简要说明,我们自己来实现一个ArrayList,省略泛型实现,并且只实现addremove方法,用来添加元素和删除元素,最后再实现我们要讲解的deduplicate去重方法。

MyArrayList实现

public class MyArrayList {
    private int size; // 规模
    private Object[] elems; // 数据区
	
	/**
     * 构造数组容量为c的ArrayList
     *
     * @param c 数组容量
     */
    public MyArrayList(int c) {
        this.elems = new Object[c];
        this.size = 0;
    }

    /**
     * 添加元素
     * @param o 元素对象
     */
    public void add(Object o) {
        if (this.elems.length <= this.size) {
            throw new ArrayIndexOutOfBoundsException("数组容量已满,请勿添加元素");
        }
        this.elems[size++] = o;
    }

    /**
     * 删除索引为i的元素
     * @param i 索引
     * @return 索引为i的元素
     */
    public Object remove(int i) {
        if (i < 0 || this.size <= i) {
            throw new ArrayIndexOutOfBoundsException("数组大小为" + this.size);
        }
        Object o = elems[i];
        System.arraycopy(elems, i + 1, elems, i, this.size - (i + 1));
        size--;
        return o;
    }

    /**
     * toString方法实现,方便我们查看数组元素情况
     * @return
     */
    @Override
    public String toString() {
        if (elems == null) {
            return "null";
        }

        int iMax = size - 1;
        if (iMax == -1) {
            return "[]";
        }

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(elems[i]);
            if (i == iMax) {
                return b.append(']').toString();
            }
            b.append(", ");
        }
    }
}

我们先来测试一下add方法,往数组里面添加10个元素,如下:

MyArrayList myList = new MyArrayList(10);
for (int i = 0; i < 10; i++) {
    // 依次添加0~9的元素
    myList.add(i);
}
System.out.println(myList);

打印情况如下:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

符合我们的预期。
再来测试一下remove方法,在上述add方法后的基础上删除索引为5的元素,如下:

myList.remove(5);
System.out.println(myList);

打印情况如下:

[0, 1, 2, 3, 4, 6, 7, 8, 9]

符合我们的预期。

去重方法实现

借助remove方法实现

因为我们只能借助内部数组,所以在判断元素是否存在的时候,我们要去遍历数组判断之前是否出现过该元素,步骤如下:

  1. 从索引为1位置开始遍历数组元素(因为索引为0时,只有一个元素,无需判断)
  2. 对于第i个元素,到该数组中从头开始查找之前是否出现过该元素
  3. 如果出现了该元素,调用remove方法删除
  4. 遍历结束

代码实现如下:

/**
 * 去重
 */
public void deduplicate() {
    if (this.size <= 1) {
        // 只有一个元素,无需去重,直接返回
        return;
    }
    // 从索引为1开始遍历数组元素
    for (int i = 1; i < size; i++) {
        for (int j = 0; j < i; j++) {
            // 对于第i个元素,到该数组中继续从头开始查找之前是否出现过该元素
            if (Objects.equals(elems[j], elems[i])) {
                // 之前出现过该元素,调用remove方法将它删除
                remove(i);
                // 如果元素删除后,遍历索引也需要减一个元素大小,这步很容易遗漏,要注意
                i--;
                // 已经判定有重复元素,并且已经删除,查找过程结束
                break;
            }
        }
    }
}

测试代码如下:

MyArrayList myList = new MyArrayList(20);
myList.add(123);
myList.add(24);
myList.add(42);
myList.add(24);
myList.add(123);
myList.add(56);
myList.add(92);
myList.add(68);
myList.add(24);
myList.add(56);
myList.add(73);
System.out.println(myList);
myList.deduplicate();
System.out.println(myList);

运行结果如下:

[123, 24, 42, 24, 123, 56, 92, 68, 24, 56, 73]
[123, 24, 42, 56, 92, 68, 73]

符合我们的预期。

标记清除法实现

如果使用remove方法实现,在每次删除元素的时候,都要O(n)的算法复杂度,这里我们可以暂时先把元素记下来,不直接删除,在最后遍历结束时,统一进行删除。我们这里可以用null进行标记(如果不允许元素为null的话),表示元素为待删除元素。

元素移动过程如下所示,绿色表示就位元素(已移动完成):
在这里插入图片描述
最后绿色的部分表示我们需要的元素。

代码实现如下:

public void deduplicate() {
    if (this.size <= 1) {
        // 只有一个元素,无需去重,直接返回
        return;
    }
    // 重复元素个数
    int dupCnts = 0;
    // 从1开始遍历数组元素
    for (int i = 1; i < size; i++) {
        for (int j = 0; j < i; j++) {
            if (Objects.equals(elems[j], elems[i])) {
                // 之前出现过该元素,将它标记为null
                elems[i] = null;
                // 重复元素个数+1
                dupCnts++;
                break;
            }
        }
    }
    if (dupCnts == 0) {
    	// 没有重复元素
    	return;
    }
    int j = 1; // null元素区间开始位置
    int k = 1; // 非null元素区间开始位置
    int len = 0; // 非null元素区间长度
    // 删除标记为null的元素
    for (int i = 1; // 遍历索引
         i < size;
         i++) {
        if (elems[i] == null) {
            // 如果为null元素
            if (len > 0) {
                // 如果有待移动的非null元素
                if (j < k) {
                    // 如果需要移动,如果j==k则不需要移动
                    // 将非null区间元素移到null元素区间开始的位置
                    System.arraycopy(elems, k, elems, j, len);
                }
                // 移动之后null元素的开始位置需要更新
                j = j + len;
                // 移动完毕,非null元素区间长度重置为0
                len = 0;
            }
        } else {
            // 如果为非null元素
            if (len == 0) {
                // 如果非null区间元素长度为0,表示一个新的非null元素区间开始
                // 更新k的值
                k = i;
            }
            // 非null元素区间长度+1
            len++;
        }
    }
    if (len > 0) {
        // 需要处理末尾剩余元素
        // 这里j肯定小于k,无需判断
        System.arraycopy(elems, k, elems, j, len);
    }
    // 因为删除了重复元素,所以数组大小需要减去相应元素个数
    size -= dupCnts;
}

测试结果与上述结果相同。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值