Skip to content

Commit

Permalink
Implemented 'Circular Array Loop' challenge
Browse files Browse the repository at this point in the history
  • Loading branch information
wibosco committed Sep 4, 2024
1 parent 14894f8 commit d674e0a
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 4 deletions.
8 changes: 8 additions & 0 deletions LeetCode/LeetCode.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,8 @@
432592232C88832B00F1340A /* SortTransformedArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432592222C88832B00F1340A /* SortTransformedArrayTests.swift */; };
432592252C88B70300F1340A /* StringCompression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432592242C88B70300F1340A /* StringCompression.swift */; };
432592272C88B72F00F1340A /* StringCompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432592262C88B72F00F1340A /* StringCompressionTests.swift */; };
432592292C88EBA300F1340A /* CircularArrayLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432592282C88EBA300F1340A /* CircularArrayLoop.swift */; };
4325922B2C88EBD700F1340A /* CircularArrayLoopTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4325922A2C88EBD700F1340A /* CircularArrayLoopTests.swift */; };
432853762AF6F7C3005531E8 /* MinimumPenaltyForAShop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432853752AF6F7C3005531E8 /* MinimumPenaltyForAShop.swift */; };
432853782AF6F7EB005531E8 /* MinimumPenaltyForAShopTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432853772AF6F7EB005531E8 /* MinimumPenaltyForAShopTests.swift */; };
432985982C133EBB002C4D37 /* SortArrayByParity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432985972C133EBB002C4D37 /* SortArrayByParity.swift */; };
Expand Down Expand Up @@ -1645,6 +1647,8 @@
432592222C88832B00F1340A /* SortTransformedArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortTransformedArrayTests.swift; sourceTree = "<group>"; };
432592242C88B70300F1340A /* StringCompression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringCompression.swift; sourceTree = "<group>"; };
432592262C88B72F00F1340A /* StringCompressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringCompressionTests.swift; sourceTree = "<group>"; };
432592282C88EBA300F1340A /* CircularArrayLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularArrayLoop.swift; sourceTree = "<group>"; };
4325922A2C88EBD700F1340A /* CircularArrayLoopTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularArrayLoopTests.swift; sourceTree = "<group>"; };
432853752AF6F7C3005531E8 /* MinimumPenaltyForAShop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimumPenaltyForAShop.swift; sourceTree = "<group>"; };
432853772AF6F7EB005531E8 /* MinimumPenaltyForAShopTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimumPenaltyForAShopTests.swift; sourceTree = "<group>"; };
432985972C133EBB002C4D37 /* SortArrayByParity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortArrayByParity.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2201,6 +2205,7 @@
437760492AEBFEE40028692C /* CheckIfMoveIsLegal.swift */,
4335FB122B8944D500416BB6 /* CheckIfNAndItsDoubleExist.swift */,
43D24C2D2C8500C100CC83B6 /* CheckIfStringIsAPrefixOfArray.swift */,
432592282C88EBA300F1340A /* CircularArrayLoop.swift */,
3D5C913627A7F7630035C399 /* ClimbingStairs.swift */,
3D5C913A27A7F7630035C399 /* CloneBinaryTreeWithRandomPointer.swift */,
3D5C90B827A7F7620035C399 /* CloneGraph.swift */,
Expand Down Expand Up @@ -2714,6 +2719,7 @@
4377604B2AEBFF170028692C /* CheckIfMoveIsLegalTests.swift */,
4335FB142B89455100416BB6 /* CheckIfNAndItsDoubleExistTests.swift */,
43D24C2F2C85089600CC83B6 /* CheckIfStringIsAPrefixOfArrayTests.swift */,
4325922A2C88EBD700F1340A /* CircularArrayLoopTests.swift */,
3D5C920727A7F76B0035C399 /* ClimbingStairsTests.swift */,
3D5C928527A7F76C0035C399 /* CloneBinaryTreeWithRandomPointerTests.swift */,
3D5C922D27A7F76B0035C399 /* CloneGraphTests.swift */,
Expand Down Expand Up @@ -3457,6 +3463,7 @@
43EE0BD22A546240003D23E0 /* DivideArrayInSetsOfKConsecutiveNumbers.swift in Sources */,
3D5C914727A7F7630035C399 /* TreeNodeNext.swift in Sources */,
3D5C918727A7F7630035C399 /* ValidPerfectSquare.swift in Sources */,
432592292C88EBA300F1340A /* CircularArrayLoop.swift in Sources */,
3DC827F827C7CD600092ACDC /* MultiplyStrings.swift in Sources */,
3D5C91E327A7F7630035C399 /* RottingOranges.swift in Sources */,
3D5C915C27A7F7630035C399 /* MaximumSwap.swift in Sources */,
Expand Down Expand Up @@ -4051,6 +4058,7 @@
436634B22AFE8EC30087F13F /* CousinsInBinaryTreeTests.swift in Sources */,
3D5C92F527A7F76C0035C399 /* MiddleOfLinkedListTests.swift in Sources */,
3D5C92EB27A7F76C0035C399 /* FindTheTownJudgeTests.swift in Sources */,
4325922B2C88EBD700F1340A /* CircularArrayLoopTests.swift in Sources */,
4379F48B29E802E8003BA6F9 /* HouseRobberTests.swift in Sources */,
3DC6177427F3AC330019DDD1 /* UndergroundSystemTests.swift in Sources */,
3D5C92A227A7F76C0035C399 /* AddTwoNumbersTests.swift in Sources */,
Expand Down
124 changes: 124 additions & 0 deletions LeetCode/LeetCode/Challenges/CircularArrayLoop.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// CircularArrayLoop.swift
// LeetCode
//
// Created by William Boles on 04/09/2024.
//

import Foundation

//https://leetcode.com/problems/circular-array-loop/
struct CircularArrayLoop {

//Time: O(n ^ 2) where n is the number of elements in `nums`
//Space: O(1)
//array
//linked list
//iterative
//multi-source
//slow and fast pointer
//two pointers
//modulo
//
//Solution Description:
//Treating `nums` as a linked list(s) as each index/node goes to only one other node, we can use slow and fast pointers to
//traverse the linked list and determine if there is a cycle. An array is only considered circular if the loop is greater
//than one node and all movements in that path are in the same direction i.e. all positive or all negative jumps.
//Calculating the destination of any given node involves wrapping that jump by using an modulo operation. Once we have our
//destination we check that it is not a single node loop and that the new jump is in the same direction. If both conditions
//are true we return it; if either condition is false we return nil, causing the while loop to fail and a new source node to
//selected (`nums` can contained multiple unconnected linked list). If a cycle exists in the linked list, the `fast` pointer
//will eventually catch the `slow` pointer resulting in the while loop failing and true being returned. If after iterating
//through all posible source nodes we haven't found a cycle we return false.
func circularArrayLoop(_ nums: [Int]) -> Bool {
for i in 0..<nums.count {
var slow: Int? = i
var fast: Int? = i

repeat {
slow = calculateDestination(slow, nums)
fast = calculateDestination(calculateDestination(fast, nums), nums)
} while slow != fast && slow != nil && fast != nil

//for there to be a loop fast and slow should never be nil
if slow != nil && slow == fast {
return true
}
}

return false
}

private func calculateDestination(_ src: Int?, _ nums: [Int]) -> Int? {
guard let src else {
return nil
}

let dest = mod((src + nums[src]), nums.count)

//not a single node loop
guard src != dest else {
return nil
}

//same direct?
guard (nums[src] > 0) == (nums[dest] > 0) else {
return nil
}

return dest
}

//% in Swift is a remainder operator not a modulo operator so doesn't handle negatives as
//a modulo operator would, the below method corrects that
private func mod(_ a: Int, _ n: Int) -> Int {
let r = a % n
return r >= 0 ? r : r + n
}

//Time: O(n ^ 2) where n is the number of elements in `nums`
//Space: O(n)
//array
//graph theory
//DFS
//recusive
//multi-source
//visited
//modulo
//
//Solution Description:
//Treating `nums` as a graph(s) we can traverse the possible paths in a DFS manner. This is a simple DFS traversal as we
//know that each node has only one neighbor. An array is only considered circular if the loop is greater than one node and
//all movements in that path are in the same direction i.e. all positive or all negative jumps. Calculating the destination
//of any given node involves wrapping that jump by using an modulo operation. Once we have our destination we check that it
//is not a single node loop and that the new jump is in the same direction. If both conditions are true we return it; if
//either condition is false we return nil and begin a new DFS traversal from a different source node. Next we checked if the
//new destination node has already been visited, if it has been visited then we have a valid loop and can return true; if it
//hasn't been visited we add it to `visited` and repeat the process with the `dest` as the new `src` node. If we iterate
//through all possible starting nodes and haven't found a loop we return false.
func circularArrayLoopDFS(_ nums: [Int]) -> Bool {
for i in 0..<nums.count {
var visited = Set<Int>()

if dfs(i, nums, &visited) {
return true
}
}

return false
}

private func dfs(_ src: Int, _ nums: [Int], _ visited: inout Set<Int>) -> Bool {
guard let dest = calculateDestination(src, nums) else {
return false
}

guard !visited.contains(dest) else {
return true
}

visited.insert(dest)

return dfs(dest, nums, &visited)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct ConvertSortedListToBinarySearchTree {
//DFS
//recursive
//sorted
//fast and slow pointers
//slow and fast pointers
//divide and conquer
//three pointers
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct MaximumEnemyFortsThatCanBeCaptured {
if value == 0 {
currentCapturedForts += 1
} else {
if (value == -1 && startingBoundary == 1) || (value == 1 && startingBoundary == -1) {
if (value == -1 && startingBoundary == 1) || (value == 1 && startingBoundary == -1) {
//found a matching end to our start
maxCapturedForts = max(maxCapturedForts, currentCapturedForts)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct MaximumTwinSumOfALinkedList {
//Time: O(n) where n is the number of nodes in the linked list
//Space: O(1)
//linked list
//fast and slow pointers
//slow and fast pointers
//two pointers
//
//Solution Description:
Expand Down
2 changes: 1 addition & 1 deletion LeetCode/LeetCode/Challenges/SortList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct SortList {
//sorted
//recursion
//three pointers
//fast and slow pointers
//slow and fast pointers
//merge sort
//divide and conquer
//sentinel head
Expand Down
39 changes: 39 additions & 0 deletions LeetCode/LeetCodeTests/Tests/CircularArrayLoopTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// CircularArrayLoopTests.swift
// LeetCodeTests
//
// Created by William Boles on 04/09/2024.
//

import XCTest

@testable import LeetCode

final class CircularArrayLoopTests: XCTestCase {

// MARK: - Tests

func test_A() {
let nums = [2,-1,1,2,2]

let result = CircularArrayLoop().circularArrayLoop(nums)

XCTAssertTrue(result)
}

func test_B() {
let nums = [-1,-2,-3,-4,-5,6]

let result = CircularArrayLoop().circularArrayLoop(nums)

XCTAssertFalse(result)
}

func test_C() {
let nums = [1,-1,5,1,4]

let result = CircularArrayLoop().circularArrayLoop(nums)

XCTAssertTrue(result)
}
}

0 comments on commit d674e0a

Please sign in to comment.