Showing posts with label Problem Solving. Show all posts
Showing posts with label Problem Solving. Show all posts

Saturday, September 14, 2024

Minimum Number of Refills for a Car Journey

You are driving to a destination that is a certain distance away, and your car has a limited fuel tank capacity. Along the way, there are gas stations at specific distances from your starting point. The task is to determine the **minimum number of refills** you will need to reach the destination, if possible, based on the given car fuel capacity and the locations of the gas stations.

### Inputs:
1. **Distance to destination** (`dist`): The total distance you need to travel.
2. **Fuel tank capacity** (`miles`): The maximum number of miles your car can travel on a full tank.
3. **Number of gas stations** (`n`): The number of gas stations along the route.
4. **Gas station locations** (`gas_stations`): A list of distances where gas stations are located from your starting point.

### Objective:
Determine the minimum number of refills required to reach the destination. If it’s not possible to reach the destination, return `-1`.

### Solution

1. **Initial Setup:**
   - You start with a full tank of gas, so the car can travel `miles` distance before needing a refill.
   - The variable `num_refill` keeps track of the total number of refills.
   - `curr_refill` tracks your current position at a gas station, and `limit` keeps track of how far you can travel with the current fuel level.

2. **Loop Until the Destination is Reachable:**
   - While the destination is beyond your current limit (i.e., `limit < dist`), you need to check whether you can reach the destination or the next gas station.
   - If you can't reach the next gas station (or there are no more stations), return `-1` to indicate that the trip is impossible.

3. **Find the Furthest Reachable Gas Station:**
   - The algorithm moves to the furthest reachable gas station that is within the current fuel limit.
   - It iterates through the list of gas stations and moves `curr_refill` to the furthest station within range.
   
4. **Refill at the Furthest Station:**
   - After finding the furthest gas station within reach, refill the tank, increase the refill count, and update the `limit` to reflect how far you can now travel after refilling.

5. **Continue Until the Destination is Reachable:**
   - The loop continues, finding the next furthest reachable station and refilling until you can reach the destination.

6. **Return the Number of Refills:**
   - If you reach the destination, return the number of refills made during the journey.

### Step-by-Step Breakdown

1. **Check Feasibility**:
   - Start with a full tank, and as long as the destination cannot be reached with the current fuel (`limit < dist`), look for gas stations within range.

2. **Handle Edge Cases**:
   - If the gas station is too far away from the current limit or there are no more gas stations within reach, return `-1` to indicate that the destination is unreachable.

3. **Optimize Refills**:
   - Always stop at the furthest reachable gas station to minimize the number of refills. This prevents unnecessary stops at closer stations.

### Example Walkthrough:

#### Test Case 1:
- **Input:** `car_fueling(950, 400, 4, [200, 375, 550, 750])`
  - **Explanation:**
    - Start with a full tank (can travel 400 miles).
    - Reach the first station at 200 miles, but continue to the furthest station at 375 miles.
    - Refill at 375 miles and continue.
    - Reach the next station at 550 miles but continue to the furthest one at 750 miles.
    - Refill at 750 miles and then reach the destination at 950 miles.
  - **Result:** 2 refills are needed.

#### Test Case 2:
- **Input:** `car_fueling(10, 3, 4, [1, 2, 5, 9])`
  - **Explanation:**
    - Start with a full tank (can travel 3 miles).
    - The first station is at 1 mile, but you cannot reach the next station at 5 miles with the current fuel capacity.
  - **Result:** The trip is impossible, so return `-1`.

Saturday, August 3, 2024

When Dynamic Programming May Not Be the Best Choice

When NOT to Use Dynamic Programming – Complete Guide

๐Ÿšซ When Dynamic Programming is NOT the Right Choice

Dynamic Programming (DP) is often seen as a “go-to” technique for optimization problems. But here’s the truth:

Using DP blindly can make your solution slower, more complex, and harder to maintain.

This guide helps you understand when NOT to use DP—and what to use instead.


๐Ÿ“š Table of Contents


⚡ Quick Refresher

DP works best when two conditions are met:

  • Overlapping subproblems
  • Optimal substructure

Mathematically:

\[ DP(n) = \min / \max (DP(n-1), DP(n-2), ...) \]

If your problem doesn’t follow this pattern, DP may not help.


๐Ÿšจ When Dynamic Programming is NOT Ideal

1. Simple Problems

If a problem can be solved in one pass:

\[ Time = O(n) \]

Using DP may unnecessarily increase complexity.

---

2. Small Input Size

For small \( n \), even brute force works:

\[ O(n^2) \approx O(n) \quad \text{(for small n)} \]

๐Ÿ‘‰ Optimization doesn’t matter when input is tiny.
---

3. No Overlapping Subproblems

If each subproblem is unique:

\[ Total\ Work = Unique\ Subproblems \]

No repetition → No benefit from memoization.

---

4. Greedy Works Better

Some problems follow:

\[ Global\ Optimum = Local\ Optimum \]

In such cases, greedy is faster and simpler.

---

5. Memory Constraints

DP space usage:

\[ Space = O(n) \text{ or } O(n^2) \]

If memory is limited, this is a problem.

---

6. Changing Inputs

If inputs keep changing:

\[ Recompute\ Cost = O(n) \]

DP tables must be rebuilt → inefficient.

---

7. Low Recursion Depth

If recursion is shallow:

\[ Depth \approx 1 \text{ or } 2 \]

No repeated work → DP unnecessary.

---

8. Hard to Define State

If you cannot define:

\[ State = f(parameters) \]

DP becomes messy and impractical.


๐Ÿ“ Time vs Space Trade-off

DP improves time but increases space:

\[ Time_{DP} < Time_{Brute} \]

\[ Space_{DP} > Space_{Brute} \]

๐Ÿ‘‰ You are trading memory for speed.

๐Ÿ’ป Code Comparison

Without DP (Simple Loop)

def sum_array(arr): total = 0 for x in arr: total += x return total

With DP (Unnecessary)

memo = {} def sum_dp(i, arr): if i == len(arr): return 0 if i in memo: return memo[i] memo[i] = arr[i] + sum_dp(i+1, arr) return memo[i]

๐Ÿ–ฅ️ CLI Output

Click to Expand
Input: [1,2,3,4]
Simple Loop Output: 10
DP Output: 10
Time: Loop faster, DP slower due to overhead

๐Ÿ› ️ Better Alternatives

  • Greedy Algorithms → Fast and simple
  • Divide & Conquer → Efficient recursion
  • Brute Force → Fine for small inputs
  • Graph Algorithms → When structure matters

๐Ÿ’ก Key Takeaways

  • DP is powerful—but not always necessary
  • Check problem structure before using DP
  • Greedy and simple methods often win
  • Always balance time vs space

๐ŸŽฏ Final Thought

Smart engineers don’t just solve problems—they choose the right tool.

DP is powerful… but knowing when NOT to use it is even more powerful.

Featured Post

How HMT Watches Lost the Time: A Deep Dive into Disruptive Innovation Blindness in Indian Manufacturing

The Rise and Fall of HMT Watches: A Story of Brand Dominance and Disruptive Innovation Blindness The Rise and Fal...

Popular Posts