数据结构与算法-差分数组及应用

差分数组

差分数组: 其实差分数组是创建一个一个辅助数组,用来表示给定数组的变化,一般用来对数组进行区间修改的操作。

频繁操作数组区间的问题

假设我们要对一个数组进行区间操作。数组为 a = {10,10, 20,20,50,… 100}。数组数据比较多。

  1. 对数组的1~3项做加5操作(数组从0开始)

    遍历数组第1项~5项: a = {10,15,25,25,50,…}

  2. 对数组2~4项做加3操作

    遍历数组第2~4项: a = {10, 15, 28,28, 53,…}

    。。。。。。。。。。。。。。。。

我们发现大量的频繁的区间内操作每次都要对数据的指定位置进行遍历及运算,这样如果数组过大操作过于频繁会造成数组的响应时间过长,时间复杂度升高。

使用差分数组优化操作

创建差分数组

image-20240620104349178

所谓差分数组即数组中的每一项(除第0项)减去其前一项构成一个新的数组,此数组为差分数组。

在某个区间进行操作

先对区间[1,3]进行+5操作,我们发现差分数组diff[2] = 0, diff[3] = 10; diff[1] = 10+15(即区间的开始位置),diff[4]=30-5(即区间末尾+1)。

在对区间[2,4] 进行+8操作, 我们发现差分数组diff[3]=10, diff[4] = 25,diff[2]=0+8(区间起始位置2),diff[5]= 10-2=8(区间末尾加1)

image-20240620114446754

差分数组特性

特性1: 给区间[m, n]进行K数据操作

由图我们可以看出每次查分数组 a[start, end] 的操作数组, 从 m+1~n 数据不会发生变化,变化的只是a[m]和a[n+1]位置,且m位置值为原a[m]+K, a[n+1]-K, K值是区间内的每个数组元素的操作数据。

对a[m]+K,则对应的需要 a[n+1]-K

也就是说在差分数组中只有第m项和第n+1项的数据会发生变化(前提: 需要在区间内操作的数据相同)。

特性2: 原数组的每个数据值和差分数组的关系

a[n] = diff(Sum(n-1)) + diff[n]; 原数组当前数据 = 差分数组前项和+ 差分数组当前项

image-20240620162956291

a[1] = diff[0] + diff[1] = diff(Sum(0))+diff[1]= 0 + 15 = 15 = a[0] + diff[1];

a[2] = diff[0] + diff[1] + diff[2] =diff(Sum(1))+diff[2]= 0 + 15+8 = 23 = a[1] + diff[2];

a[3] = diff(Sum(2)) + diff[3] = 23 + 10 = a[2] + diff[3];

:

a[n] = diff(Sum(n-1)) + diff[n] = a[n-1] + diff[n]

差分数组使用

定义一个初始数组:int[] source = {0, 0, 0, 0, 0, 0, 0};

image-20240620172150139

获取差分数组

定义差分数组: int[] diff = new int[source.length];

image-20240620172202301

public static void diffExecute(int[] diff, int[] source) {
    for (int i = 1; i < source.length; i++) {
        diff[i] = source[i] - source[i - 1];
    }
}

区间[1,2]+10操作

在区间[1,2]中每一个数据+10,原数组变化为:

image-20240620172409924

  for (int i = 1; i <= 2; i++) source[i] += 10;

差分数组根据性质1: 只有[m,n]m位置和n+1位置会有数据改动。重构差分数组

image-20240620173648198

public static void diffx(int[] diff, int m, int n, int k) {
    diff[m] += k;  //区间起始点
    if (n < diff.length - 1) {
        diff[n + 1] -= k; //区间终止点的下一个位置
    }
}

区间[2,3]做+20操作

在区间[2,3]中每一个数据+20,原数组变化为:

image-20240620173010921

  for (int i = 1; i <= 2; i++) source[i] += 20;

差分数组变化,

image-20240620173703966

区间[2,5]做+25操作

在区间[2,3]中每一个数据+25,原数组变化为:

image-20240620173401060

  for (int i = 2; i <=5; i++) source[i] += 25;

差分数组变化

image-20240620173722908

由差分数组还原原数组计算后结果

原数组经过了[1,2]区间+10,[2,3]区间+20和[2,5]区间+25操作;我们同时也记录了差分数组的变化结果。接下来使用差分数组反推原数组的结果。此处我们使用差分数组的第2个特性: 当前项=前项和+当前项, diff[i] = diff[i-1] +diff[i]; 为程序清晰,我定义sum来记录前项和(可以自行简化代码)。

image-20240620174804995

int sum = diff[0];
for (int i = 1; i < dest.length; i++) {
    sum = sum + diff[i];
    diff[i] = sum;
}

我们对比一下原始数据经过一系列变化后,发现和最后的结果是一致的。

image-20240620175103609

经典问题

1109. 航班预订统计 - 力扣(LeetCode)

image-20240620175238399

解题思路使用差分数组

由于在某个区间内会频繁进行数据的操作,可以创建一个差分辅助数组利用性质2,只操作m项和n+1项(m,为区间起始位置,n为区间终止位置的下一个位置)。

public int[] corpFlightBookings(int[][] bookings, int n) {
    int[] diff = new int[n];

    for (int i = 0; i < bookings.length; i++) {  //遍历二维数组找到区间和要修改的值
        int[] booking = bookings[i];
        int val = booking[2];
        int start = booking[0];
        int end = booking[1];
        
        diff[start - 1] += val; //start-1是日期从1开始,我们的数组从0开始,利用差分数组性质1:记录起始位置增量

        if (end < n) {
            diff[end] -= val;   //性质1: 和起始位置进行相反操作,不用+1是因为起始位置左移了一个位置。
        }
    }

    /*
     *  差分数组性质2:  前项和+当前项
     */
    for (int i = 1; i < diff.length; i++) { 
        diff[i] = diff[i] + diff[i - 1];
    }

    return diff;

}
  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个使用Java实现的“小猫钓鱼”纸牌游戏,其中使用了链表等数据结构算法: ```java import java.util.*; public class CatFishingGame { // 定义扑克牌花色和数字 private static final String[] SUITS = {"♠️", "♥️", "♣️", "♦️"}; private static final String[] VALUES = {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"}; // 定义玩家手牌和出牌堆 private LinkedList<String> playerHand; private LinkedList<String> computerHand; private LinkedList<String> tableCards; // 初始化一副扑克牌 private LinkedList<String> initDeck() { LinkedList<String> deck = new LinkedList<>(); for (String suit : SUITS) { for (String value : VALUES) { deck.add(value + suit); } } Collections.shuffle(deck); return deck; } // 发牌 private void dealCards(LinkedList<String> deck) { playerHand = new LinkedList<>(); computerHand = new LinkedList<>(); for (int i = 0; i < 6; i++) { playerHand.add(deck.pop()); computerHand.add(deck.pop()); } tableCards = new LinkedList<>(); tableCards.add(deck.pop()); } // 玩家出牌 private void playerPlayCard() { Scanner scanner = new Scanner(System.in); System.out.println("你的手牌是:" + playerHand); System.out.println("桌面上的牌是:" + tableCards.peekLast()); System.out.print("请选择一张牌出牌:"); String card = scanner.nextLine(); if (!playerHand.contains(card)) { System.out.println("你的手牌中没有这张牌,请重新出牌!"); playerPlayCard(); } else if (!isValid(card)) { System.out.println("出牌不合法,请重新出牌!"); playerPlayCard(); } else { playerHand.remove(card); tableCards.add(card); } } // 判断出牌是否合法 private boolean isValid(String card) { String lastCard = tableCards.peekLast(); String value = card.substring(0, card.length() - 1); String suit = card.substring(card.length() - 1); String lastValue = lastCard.substring(0, lastCard.length() - 1); return value.equals(lastValue) || suit.equals(lastCard.substring(lastCard.length() - 1)); } // 电脑出牌 private void computerPlayCard() { String lastCard = tableCards.peekLast(); for (String card : computerHand) { String value = card.substring(0, card.length() - 1); String suit = card.substring(card.length() - 1); String lastValue = lastCard.substring(0, lastCard.length() - 1); if (value.equals(lastValue) || suit.equals(lastCard.substring(lastCard.length() - 1))) { computerHand.remove(card); tableCards.add(card); System.out.println("电脑出牌:" + card); return; } } String card = computerHand.pop(); tableCards.add(card); System.out.println("电脑出牌:" + card); } // 判断游戏是否结束 private boolean isGameOver() { return playerHand.isEmpty() || computerHand.isEmpty(); } // 计算分数 private int calculateScore() { int score = 0; for (String card : tableCards) { String value = card.substring(0, card.length() - 1); if (value.equals("A")) { score += 1; } else if (value.equals("10") || value.equals("J") || value.equals("Q") || value.equals("K")) { score += 10; } else { score += Integer.parseInt(value); } } return score; } // 开始游戏 public void startGame() { LinkedList<String> deck = initDeck(); dealCards(deck); while (!isGameOver()) { playerPlayCard(); if (isGameOver()) { break; } computerPlayCard(); } int playerScore = calculateScore(); int computerScore = calculateScore(); System.out.println("你的得分是:" + playerScore); System.out.println("电脑的得分是:" + computerScore); if (playerScore > computerScore) { System.out.println("恭喜你,你赢了!"); } else if (playerScore < computerScore) { System.out.println("很遗憾,你输了!"); } else { System.out.println("平局!"); } } public static void main(String[] args) { CatFishingGame game = new CatFishingGame(); game.startGame(); } } ``` 运行结果类似于: ``` 你的手牌是:[5♦️, 10♠️, 7♥️, 3♦️, J♦️, 6♣️] 桌面上的牌是:8♣️ 请选择一张牌出牌:6♣️ 电脑出牌:10♣️ 你的手牌是:[5♦️, 10♠️, 7♥️, 3♦️, J♦️] 桌面上的牌是:8♣️10♣️ 请选择一张牌出牌:J♦️ 电脑出牌:J♠️ 你的手牌是:[5♦️, 10♠️, 7♥️, 3♦️] 桌面上的牌是:8♣️10♣️J♠️ 请选择一张牌出牌:7♥️ 电脑出牌:9♥️ 你的手牌是:[5♦️, 10♠️, 3♦️] 桌面上的牌是:8♣️10♣️J♠️9♥️ 请选择一张牌出牌:10♠️ 电脑出牌:7♠️ 你的手牌是:[5♦️, 3♦️] 桌面上的牌是:8♣️10♣️J♠️9♥️7♠️ 请选择一张牌出牌:3♦️ 你的手牌是:[5♦️] 桌面上的牌是:8♣️10♣️J♠️9♥️7♠️3♦️ 请选择一张牌出牌:5♦️ 你的得分是:51 电脑的得分是:52 很遗憾,你输了! ``` 希望这个简单的实现可以帮助你更好地理解数据结构算法应用

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值